feat: advance browser agent integration workflows

This commit is contained in:
go0p
2026-08-06 15:11:35 +08:00
committed by go0p
parent af5a4db694
commit 0c8e1c7b69
215 changed files with 35137 additions and 5442 deletions
+105
View File
@@ -0,0 +1,105 @@
import {mkdtemp, readFile, rm} from 'node:fs/promises'
import {tmpdir} from 'node:os'
import {join, resolve} from 'node:path'
import {chromium} from 'playwright-core'
import {resolveChromiumPath} from './resolve-chromium.mjs'
const root = resolve(import.meta.dirname, '..')
export async function extensionRequest(page, action, payload = {}) {
return page.evaluate(async ({requestAction, requestPayload}) => {
const response = await chrome.runtime.sendMessage({action: requestAction, payload: requestPayload})
if (!response?.ok) throw new Error(response?.error?.message || response?.error || requestAction)
return response.data
}, {requestAction: action, requestPayload: payload})
}
export async function waitFor(page, action, payload, predicate, timeoutMs = 15_000) {
const deadline = Date.now() + timeoutMs
let value
while (Date.now() < deadline) {
value = await extensionRequest(page, action, payload)
if (predicate(value)) return value
await page.waitForTimeout(150)
}
throw new Error(`Timed out waiting for ${action}: ${JSON.stringify(value)}`)
}
export async function launchBrowserAgentContractHarness({
profilePrefix,
targetURL,
extensionPath = resolve(root, process.env.EXTENSION_PATH || '.output/chrome-mv3-enterprise'),
}) {
const manifest = JSON.parse(await readFile(resolve(extensionPath, 'manifest.json'), 'utf8'))
const executablePath = await resolveChromiumPath()
const userDataDir = await mkdtemp(join(tmpdir(), profilePrefix))
const context = await chromium.launchPersistentContext(userDataDir, {
executablePath,
headless: true,
viewport: {width: 1280, height: 760},
args: [
`--disable-extensions-except=${extensionPath}`,
`--load-extension=${extensionPath}`,
'--no-first-run',
'--no-default-browser-check',
],
})
try {
let serviceWorker = context.serviceWorkers()[0]
if (!serviceWorker) serviceWorker = await context.waitForEvent('serviceworker', {timeout: 15_000})
const extensionId = new URL(serviceWorker.url()).host
if (manifest.permissions?.includes('userScripts')) {
const extensionsPage = await context.newPage()
await extensionsPage.goto(`chrome://extensions/?id=${extensionId}`)
const toggle = extensionsPage.locator('#allow-user-scripts cr-toggle')
await toggle.waitFor({state: 'visible', timeout: 10_000})
if (!await toggle.evaluate((element) => Boolean(element.checked))) await toggle.click()
await extensionsPage.close()
}
const targetPage = await context.newPage()
targetPage.on('dialog', (dialog) => void dialog.dismiss())
await targetPage.goto(targetURL)
const controlPage = await context.newPage()
await controlPage.goto(`chrome-extension://${extensionId}/options.html`)
const tabId = await controlPage.evaluate(async (url) => {
const tabs = await chrome.tabs.query({})
return tabs.find((tab) => tab.url === url)?.id
}, targetPage.url())
if (!tabId) throw new Error(`Could not resolve target tab ${targetPage.url()}`)
return {
context,
controlPage,
extensionId,
tabId,
targetPage,
async close() {
await context.close().catch(() => undefined)
await rm(userDataDir, {recursive: true, force: true})
},
}
} catch (error) {
await context.close().catch(() => undefined)
await rm(userDataDir, {recursive: true, force: true})
throw error
}
}
export function transformedFetchOptions(execution, originalHeaders) {
const headers = new Map(originalHeaders.map((header) => [header.name.toLowerCase(), {
name: header.name,
value: header.value,
}]))
for (const name of execution.removeHeaders || []) headers.delete(name.toLowerCase())
for (const header of execution.setHeaders || []) {
headers.set(header.name.toLowerCase(), {name: header.name, value: header.value})
}
return {
method: 'POST',
headers: Object.fromEntries([...headers.values()].map((header) => [header.name, header.value])),
body: Buffer.from(execution.bodyBase64, 'base64'),
}
}
+140
View File
@@ -0,0 +1,140 @@
import {
extensionRequest,
launchBrowserAgentContractHarness,
transformedFetchOptions,
} from './browser-agent-contract-harness.mjs'
const targetURL = process.env.AES_TARGET || 'http://127.0.0.1:82/'
const plaintext = {username: 'admin', password: '123456'}
let harness
try {
harness = await launchBrowserAgentContractHarness({
profilePrefix: 'yakit-aes-contract-',
targetURL,
})
const {controlPage, tabId, targetPage} = harness
let browserRequestCount = 0
targetPage.on('request', (request) => {
if (new URL(request.url()).pathname === '/encrypt/aes.php') browserRequestCount += 1
})
await extensionRequest(controlPage, 'recording.start', {
tabId,
frameId: 0,
captureValues: true,
maxEntries: 120,
maxValueBytes: 8_192,
})
await targetPage.locator('#username').fill('admin')
await targetPage.locator('#password').fill('wrong-password')
await targetPage.getByRole('button', {name: '登录', exact: true}).click()
await targetPage.getByRole('button', {name: 'AES固定Key', exact: true}).click()
await targetPage.waitForTimeout(500)
if (browserRequestCount !== 1) {
throw new Error(`Initial AES recording expected one real request, received ${browserRequestCount}`)
}
const snapshot = await extensionRequest(
controlPage,
'recording.get',
{tabId, frameId: 0, limit: 120},
)
const candidate = snapshot.profileCandidates?.find((item) => (
item.status === 'ready'
&& new URL(item.request?.url, targetURL).pathname === '/encrypt/aes.php'
&& item.source?.callHandleId
))
if (!candidate) {
throw new Error(`Single-call AES candidate was not ready after one recording: ${JSON.stringify(snapshot)}`)
}
const callable = await extensionRequest(controlPage, 'callable.create', {
tabId,
frameId: 0,
source: 'recording',
callHandleId: candidate.source.callHandleId,
name: 'CryptoJS AES recorded call',
})
if (callable.kind !== 'recorded-call') {
throw new Error(`AES callable was not retained from the recording: ${JSON.stringify(callable)}`)
}
const plainPacket = {
method: 'POST',
url: new URL('/encrypt/aes.php', targetURL).toString(),
headers: [{name: 'Content-Type', value: 'application/json'}],
bodyBase64: Buffer.from(JSON.stringify(plaintext)).toString('base64'),
}
const proposal = await extensionRequest(controlPage, 'analysis.profile.propose', {
tabId,
frameId: 0,
candidateId: candidate.id,
callableId: callable.id,
inputPaths: ['body'],
name: 'AES deterministic contract',
})
if (proposal?.proposal?.compiler !== 'browser-transform-guided-v1') {
throw new Error(`AES Profile was not deterministically compiled: ${JSON.stringify(proposal)}`)
}
const validation = await extensionRequest(controlPage, 'analysis.profile.validate', {
tabId,
frameId: 0,
candidateId: candidate.id,
callableId: callable.id,
inputPaths: ['body'],
name: 'AES deterministic contract',
packet: plainPacket,
comparisonMode: 'structure',
})
if (!validation?.valid || !validation?.saveEligible
|| validation.proofLevel !== 'structure'
|| validation.validationDraft?.contractVersion !== 1) {
throw new Error(`AES Profile validation failed: ${JSON.stringify(validation)}`)
}
if (browserRequestCount !== 1) {
throw new Error(`AES Profile validation leaked a real browser request; observed ${browserRequestCount}`)
}
const validationDraft = await extensionRequest(
controlPage,
'analysis.profile.validation.latest',
{tabId, frameId: 0},
)
if (!validationDraft || validationDraft.id !== validation.validationDraft.id
|| validationDraft.contractVersion !== 1 || validationDraft.profile?.id) {
throw new Error(`AES Yakit handoff draft is invalid: ${JSON.stringify(validationDraft)}`)
}
const savedProfile = await extensionRequest(
controlPage,
'transform.profile.save',
validationDraft.profile,
)
const execution = await extensionRequest(controlPage, 'transform.execute', {
profileId: savedProfile.id,
direction: 'request',
packet: plainPacket,
})
const wireBody = Buffer.from(execution.bodyBase64, 'base64').toString('utf8')
const form = new URLSearchParams(wireBody)
const encryptedData = form.get('encryptedData')
if (!encryptedData || encryptedData.startsWith('{') || form.size !== 1) {
throw new Error(`AES Profile produced an invalid or nested form envelope: ${wireBody}`)
}
if (browserRequestCount !== 1) {
throw new Error(`Saved AES Profile leaked a real browser request; observed ${browserRequestCount}`)
}
const response = await fetch(
execution.url,
transformedFetchOptions(execution, plainPacket.headers),
)
const result = await response.json()
if (!result.success) throw new Error(`Target server rejected the AES Profile output: ${JSON.stringify(result)}`)
process.stdout.write(
'AES Agent contract verified: one recording produced a callable, deterministic Profile, Yakit confirmation draft, and server-accepted wire request.\n',
)
} finally {
await harness?.close()
}
+187 -81
View File
@@ -1,76 +1,23 @@
import { mkdtemp, readFile, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join, resolve } from 'node:path'
import { chromium } from 'playwright-core'
import { resolveChromiumPath } from './resolve-chromium.mjs'
import {
extensionRequest,
launchBrowserAgentContractHarness,
transformedFetchOptions,
waitFor,
} from './browser-agent-contract-harness.mjs'
const root = resolve(import.meta.dirname, '..')
const extensionPath = resolve(root, process.env.EXTENSION_PATH || '.output/chrome-mv3-enterprise')
const targetURL = process.env.AESRSA_TARGET || 'http://127.0.0.1:82/'
const manifest = JSON.parse(await readFile(resolve(extensionPath, 'manifest.json'), 'utf8'))
const executablePath = await resolveChromiumPath()
const userDataDir = await mkdtemp(join(tmpdir(), 'yakit-aesrsa-'))
let context
async function extensionRequest(page, action, payload = {}) {
return page.evaluate(async ({ requestAction, requestPayload }) => {
const response = await chrome.runtime.sendMessage({ action: requestAction, payload: requestPayload })
if (!response?.ok) throw new Error(response?.error?.message || response?.error || requestAction)
return response.data
}, { requestAction: action, requestPayload: payload })
}
async function waitFor(page, action, payload, predicate, timeoutMs = 15_000) {
const deadline = Date.now() + timeoutMs
let value
while (Date.now() < deadline) {
value = await extensionRequest(page, action, payload)
if (predicate(value)) return value
await page.waitForTimeout(150)
}
throw new Error(`Timed out waiting for ${action}: ${JSON.stringify(value)}`)
}
let harness
try {
context = await chromium.launchPersistentContext(userDataDir, {
executablePath,
headless: true,
viewport: { width: 1280, height: 760 },
args: [
`--disable-extensions-except=${extensionPath}`,
`--load-extension=${extensionPath}`,
'--no-first-run',
'--no-default-browser-check',
],
harness = await launchBrowserAgentContractHarness({
profilePrefix: 'yakit-aesrsa-',
targetURL,
})
let serviceWorker = context.serviceWorkers()[0]
if (!serviceWorker) serviceWorker = await context.waitForEvent('serviceworker', { timeout: 15_000 })
const extensionId = new URL(serviceWorker.url()).host
if (manifest.permissions?.includes('userScripts')) {
const extensionsPage = await context.newPage()
await extensionsPage.goto(`chrome://extensions/?id=${extensionId}`)
const toggle = extensionsPage.locator('#allow-user-scripts cr-toggle')
await toggle.waitFor({ state: 'visible', timeout: 10_000 })
if (!await toggle.evaluate((element) => Boolean(element.checked))) await toggle.click()
await extensionsPage.close()
}
const targetPage = await context.newPage()
targetPage.on('dialog', (dialog) => void dialog.dismiss())
const {controlPage, tabId, targetPage} = harness
let browserRequestCount = 0
targetPage.on('request', (request) => {
if (new URL(request.url()).pathname === '/encrypt/aesrsa.php') browserRequestCount += 1
})
await targetPage.goto(targetURL)
const controlPage = await context.newPage()
await controlPage.goto(`chrome-extension://${extensionId}/options.html`)
const tabId = await controlPage.evaluate(async (url) => {
const tabs = await chrome.tabs.query({})
return tabs.find((tab) => tab.url === url)?.id
}, targetPage.url())
if (!tabId) throw new Error('Could not resolve the AES+RSA target tab')
await extensionRequest(controlPage, 'recording.start', {
tabId, frameId: 0, captureValues: true, maxEntries: 120, maxValueBytes: 8_192,
@@ -130,15 +77,7 @@ try {
strategy: 'request-transaction',
callFrameId: frame.id,
name: 'sendDataAesRsa 请求事务',
transaction: {
request: {
method: candidate.request.method,
url: candidate.request.url,
expectedDestinations: candidate.sources.map((source) => source.destination).filter(Boolean),
},
inputMode: 'auto',
boundaries: ['fetch', 'xhr', 'beacon', 'form'],
},
candidateId: candidate.id,
})
if (callable.kind !== 'request-transaction' || callable.inputSlots?.[0]?.name !== 'body') {
throw new Error(`Captured callable is not a request transaction: ${JSON.stringify(callable)}`)
@@ -150,13 +89,14 @@ try {
throw new Error(`Deep-capture replay leaked a real request; observed ${browserRequestCount}`)
}
const plaintext = { username: 'admin', password: '123456' }
let execution
try {
execution = await extensionRequest(controlPage, 'callable.execute', {
tabId,
frameId: 0,
callableId: callable.id,
args: [{ username: 'admin', password: '123456' }],
args: [plaintext],
})
} catch (reason) {
const diagnostics = await controlPage.evaluate(async ({ targetTabId, callableId }) => {
@@ -209,16 +149,182 @@ try {
if (browserRequestCount !== 1) {
throw new Error(`Transaction execution leaked a real browser request; observed ${browserRequestCount}`)
}
try {
await extensionRequest(controlPage, 'callable.execute', {
tabId,
frameId: 0,
callableId: callable.id,
args: [plaintext],
})
} catch (reason) {
throw new Error(
`Captured request transaction is not repeatable: ${reason instanceof Error ? reason.message : String(reason)}`,
)
}
const response = await fetch(new URL('/encrypt/aesrsa.php', targetURL), {
const plainPacket = {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(envelope),
url: new URL('/encrypt/aesrsa.php', targetURL).toString(),
headers: [{ name: 'Content-Type', value: 'application/json' }],
bodyBase64: Buffer.from(JSON.stringify(plaintext)).toString('base64'),
}
const proposal = await extensionRequest(controlPage, 'analysis.profile.propose', {
tabId,
frameId: 0,
candidateId: candidate.id,
callableId: callable.id,
inputPaths: ['body'],
name: 'AES+RSA deterministic contract',
})
if (proposal?.proposal?.compiler !== 'browser-transform-guided-v1'
|| proposal?.profile?.request?.enabled !== true) {
throw new Error(`Deterministic profile proposal was not compiled: ${JSON.stringify(proposal)}`)
}
let validation
try {
validation = await extensionRequest(controlPage, 'analysis.profile.validate', {
tabId,
frameId: 0,
candidateId: candidate.id,
callableId: callable.id,
inputPaths: ['body'],
name: 'AES+RSA deterministic contract',
packet: plainPacket,
comparisonMode: 'structure',
})
} catch (reason) {
throw new Error(
`Deterministic profile validation could not execute: ${reason instanceof Error ? reason.message : String(reason)}; `
+ `packet=${JSON.stringify(plainPacket)}; profile=${JSON.stringify(proposal.profile)}`,
)
}
if (!validation?.valid || !validation?.saveEligible
|| validation.proofLevel !== 'structure'
|| validation.validationDraft?.contractVersion !== 1
|| !validation.validationDraft?.id) {
throw new Error(`Deterministic profile validation failed: ${JSON.stringify(validation)}`)
}
if (browserRequestCount !== 1) {
throw new Error(`Profile validation leaked a real browser request; observed ${browserRequestCount}`)
}
const validationDraft = await extensionRequest(
controlPage,
'analysis.profile.validation.latest',
{ tabId, frameId: 0 },
)
if (!validationDraft || validationDraft.contractVersion !== 1
|| validationDraft.id !== validation.validationDraft.id
|| validationDraft.profile?.id) {
throw new Error(`Yakit handoff draft is missing or already persisted: ${JSON.stringify(validationDraft)}`)
}
const savedProfile = await extensionRequest(
controlPage,
'transform.profile.save',
validationDraft.profile,
)
const profiles = await extensionRequest(controlPage, 'transform.profile.list', { tabId, frameId: 0 })
if (!savedProfile?.id || !profiles.some((profile) => profile.id === savedProfile.id)) {
throw new Error(`Confirmed profile was not persisted: ${JSON.stringify({ savedProfile, profiles })}`)
}
const profileExecution = await extensionRequest(controlPage, 'transform.execute', {
profileId: savedProfile.id,
direction: 'request',
packet: plainPacket,
})
const transformedEnvelope = JSON.parse(
Buffer.from(profileExecution.bodyBase64, 'base64').toString('utf8'),
)
for (const field of ['encryptedData', 'encryptedKey', 'encryptedIv']) {
if (typeof transformedEnvelope?.[field] !== 'string' || !transformedEnvelope[field]) {
throw new Error(`Saved profile output is missing ${field}: ${JSON.stringify(profileExecution)}`)
}
}
if (browserRequestCount !== 1) {
throw new Error(`Saved profile execution leaked a real browser request; observed ${browserRequestCount}`)
}
const response = await fetch(
profileExecution.url,
transformedFetchOptions(profileExecution, plainPacket.headers),
)
const result = await response.json()
if (!result.success) throw new Error(`Target server rejected the transaction envelope: ${JSON.stringify(result)}`)
process.stdout.write('AES+RSA request transaction verified: no browser request leaked and the target server accepted the captured envelope.\n')
if (!result.success) throw new Error(`Target server rejected the saved Profile output: ${JSON.stringify(result)}`)
await targetPage.reload()
const staleProfile = await waitFor(
controlPage,
'transform.profile.list',
{ tabId, frameId: 0 },
(items) => items?.find((item) => item.id === savedProfile.id)?.recovery?.state === 'stale',
10_000,
).then((items) => items.find((item) => item.id === savedProfile.id))
if (staleProfile.enabled || staleProfile.recovery?.capture?.automatic !== true) {
throw new Error(`Reloaded Profile did not fail closed with an automatic Recovery Plan: ${JSON.stringify(staleProfile)}`)
}
await extensionRequest(controlPage, 'transform.recovery.start', { id: savedProfile.id })
await targetPage.locator('#username').fill('admin')
await targetPage.locator('#password').fill('wrong-password')
await targetPage.getByRole('button', { name: '登录', exact: true }).click()
let recoveryClickFailure
const recoveryClick = targetPage.getByRole('button', { name: 'AES+Rsa加密', exact: true })
.click({ noWaitAfter: true, timeout: 20_000 })
.catch((reason) => { recoveryClickFailure = reason })
const recoveryPause = await waitFor(controlPage, 'deep.capture.status', {
tabId, frameId: 0,
}, (value) => value?.state === 'paused' && value.pause?.collecting !== true, 20_000)
const recoveryAutomatic = recoveryPause.pause?.automaticCapture
if (recoveryAutomatic?.state !== 'ready' || !recoveryAutomatic.frameId
|| recoveryAutomatic.strategy !== 'request-transaction') {
throw new Error(`Recovery Plan did not locate the request transaction: ${JSON.stringify(recoveryPause)}`)
}
const recovery = await extensionRequest(controlPage, 'transform.recovery.capture', {
id: savedProfile.id,
...recoveryPause.target,
callFrameId: recoveryAutomatic.frameId,
strategy: recoveryAutomatic.strategy,
})
await recoveryClick
if (recoveryClickFailure) throw recoveryClickFailure
if (recovery.state !== 'validation-required' || !recovery.pending?.callableId) {
throw new Error(`Recovery capture was not staged for validation: ${JSON.stringify(recovery)}`)
}
const recoveryValidation = await extensionRequest(controlPage, 'transform.recovery.validate', {
id: savedProfile.id,
packet: plainPacket,
})
if (recoveryValidation.recovery?.state !== 'confirmation-required'
|| !recoveryValidation.recovery.validation?.id) {
throw new Error(`Recovered Profile did not require explicit confirmation: ${JSON.stringify(recoveryValidation)}`)
}
const recoveredProfile = await extensionRequest(controlPage, 'transform.recovery.confirm', {
id: savedProfile.id,
validationId: recoveryValidation.recovery.validation.id,
})
if (recoveredProfile.id !== savedProfile.id || recoveredProfile.recovery?.state !== 'ready'
|| recoveredProfile.target.documentId === savedProfile.target.documentId) {
throw new Error(`Recovery confirmation did not atomically replace the document binding: ${JSON.stringify(recoveredProfile)}`)
}
const recoveredExecution = await extensionRequest(controlPage, 'transform.execute', {
profileId: recoveredProfile.id,
direction: 'request',
packet: plainPacket,
})
const recoveredResponse = await fetch(
recoveredExecution.url,
transformedFetchOptions(recoveredExecution, plainPacket.headers),
)
const recoveredResult = await recoveredResponse.json()
if (!recoveredResult.success) {
throw new Error(`Target server rejected the recovered Profile output: ${JSON.stringify(recoveredResult)}`)
}
process.stdout.write(
'AES+RSA Agent contract verified: evidence compiled, validated, saved, reloaded stale, recovered through one request-boundary capture, revalidated, explicitly confirmed, and accepted by the target server.\n',
)
} finally {
await context?.close().catch(() => undefined)
await rm(userDataDir, { recursive: true, force: true })
await harness?.close()
}
+251
View File
@@ -0,0 +1,251 @@
import {
extensionRequest,
launchBrowserAgentContractHarness,
transformedFetchOptions,
waitFor,
} from './browser-agent-contract-harness.mjs'
const targetURL = process.env.AESSERVER_TARGET || 'http://127.0.0.1:82/'
const keyPath = '/encrypt/server_generate_key.php'
const requestPath = '/encrypt/aesserver.php'
const plaintext = {username: 'admin', password: '123456'}
let harness
function pathOf(url) {
return new URL(url, targetURL).pathname
}
function snapshotSummary(snapshot) {
return {
events: snapshot.events?.map((event) => ({
sequence: event.sequence,
kind: event.kind,
operation: event.operation,
url: event.url ? pathOf(event.url) : undefined,
})),
candidates: snapshot.profileCandidates?.map((candidate) => ({
status: candidate.status,
request: pathOf(candidate.request?.url || ''),
prerequisites: candidate.capturePlan?.transaction?.prerequisites?.map((step) => pathOf(step.url)),
})),
}
}
function dependencySummary(snapshot, candidate) {
const eventIds = new Set(candidate.evidence?.flatMap((item) => item.eventIds || []))
return {
evidence: candidate.evidence?.filter((item) => item.kind === 'response-boundary'),
events: snapshot.events?.filter((event) => eventIds.has(event.id)).map((event) => ({
id: event.id,
sequence: event.sequence,
kind: event.kind,
operation: event.operation,
inputs: event.inputs?.map((item) => item.path),
outputs: event.outputs?.map((item) => item.path),
})),
links: snapshot.links?.filter((link) => eventIds.has(link.fromEventId) || eventIds.has(link.toEventId))
.map((link) => ({kind: link.kind, fromPath: link.fromPath, toPath: link.toPath})),
}
}
async function performLogin(targetPage, password = 'wrong-password') {
await targetPage.locator('#username').fill('admin')
await targetPage.locator('#password').fill(password)
await targetPage.getByRole('button', {name: '登录', exact: true}).click()
return targetPage.getByRole('button', {name: 'AES服务端获取Key', exact: true})
}
try {
harness = await launchBrowserAgentContractHarness({
profilePrefix: 'yakit-aesserver-',
targetURL,
})
const {controlPage, tabId, targetPage} = harness
const observed = {key: 0, terminal: 0}
targetPage.on('request', (request) => {
const path = pathOf(request.url())
if (path === keyPath) observed.key += 1
if (path === requestPath) observed.terminal += 1
})
await extensionRequest(controlPage, 'recording.start', {
tabId,
frameId: 0,
captureValues: true,
maxEntries: 160,
maxValueBytes: 8_192,
})
await (await performLogin(targetPage)).click()
await targetPage.waitForTimeout(600)
if (observed.key !== 1 || observed.terminal !== 1) {
throw new Error(`Initial operation did not produce the expected two-request flow: ${JSON.stringify(observed)}`)
}
const snapshot = await extensionRequest(
controlPage,
'recording.get',
{tabId, frameId: 0, limit: 160},
)
const candidate = snapshot.profileCandidates?.find((item) => (
item.status === 'capture-required'
&& pathOf(item.request?.url || '') === requestPath
&& item.capturePlan?.transaction?.prerequisites?.some((step) => pathOf(step.url) === keyPath)
))
if (!candidate) {
throw new Error(`Recording did not infer the online key dependency: ${JSON.stringify(snapshotSummary(snapshot))}`)
}
const transaction = candidate.capturePlan.transaction
if (transaction.version !== 2
|| transaction.prerequisites.length !== 1
|| transaction.prerequisites[0].boundary !== 'fetch'
|| transaction.prerequisites[0].response.bodyFormat !== 'json'
|| !transaction.prerequisites[0].response.requiredPaths.includes('body.aes_key')
|| !transaction.prerequisites[0].response.requiredPaths.includes('body.aes_iv')
|| transaction.request.boundary !== 'fetch'
|| pathOf(transaction.request.url) !== requestPath) {
throw new Error(`Inferred request transaction is not evidence-complete: ${JSON.stringify({
transaction,
dependency: dependencySummary(snapshot, candidate),
})}`)
}
const matcherEvent = snapshot.events?.find((event) => event.id === candidate.capturePlan.matcherEventId)
if (!matcherEvent?.crypto?.adapterId || !matcherEvent.wrapperHandleId) {
throw new Error(`Online-key candidate has no deep-capture matcher: ${JSON.stringify(candidate)}`)
}
await extensionRequest(controlPage, 'deep.capture.start', {
tabId,
frameId: 0,
matcher: {
kind: 'crypto',
adapterId: matcherEvent.crypto.adapterId,
operation: matcherEvent.crypto.operation,
wrapperHandleId: matcherEvent.wrapperHandleId,
scriptUrl: matcherEvent.scriptUrl,
frameHints: candidate.capturePlan.frameHints,
},
})
const replayButton = await performLogin(targetPage)
let replayFailure
const replay = replayButton.click({noWaitAfter: true, timeout: 20_000})
.catch((reason) => { replayFailure = reason })
const paused = await waitFor(
controlPage,
'deep.capture.status',
{tabId, frameId: 0},
(value) => value?.state === 'paused' && value.pause?.collecting !== true,
20_000,
)
const automatic = paused.pause?.automaticCapture
const frame = paused.pause?.frames?.find((item) => item.id === automatic?.frameId)
if (automatic?.state !== 'ready'
|| automatic.strategy !== 'request-transaction'
|| frame?.functionName !== 'fetchAndSendDataAes') {
throw new Error(`Deep capture selected an invalid strategy: ${JSON.stringify({automatic, functionName: frame?.functionName})}`)
}
const callable = await extensionRequest(controlPage, 'callable.create', {
tabId,
frameId: 0,
source: 'deep-capture',
strategy: 'request-transaction',
callFrameId: frame.id,
name: '在线取钥请求事务',
candidateId: candidate.id,
})
await replay
if (replayFailure) throw replayFailure
await targetPage.waitForTimeout(300)
if (observed.key !== 2 || observed.terminal !== 1) {
throw new Error(`Deep capture did not preserve the prerequisite/terminal boundary: ${JSON.stringify(observed)}`)
}
const beforeCallable = {...observed}
const callableExecution = await extensionRequest(controlPage, 'callable.execute', {
tabId,
frameId: 0,
callableId: callable.id,
args: [plaintext],
})
if (typeof callableExecution.value?.encryptedData !== 'string') {
throw new Error(`Request transaction did not return the terminal envelope: ${JSON.stringify(callableExecution)}`)
}
if (observed.key !== beforeCallable.key + 1 || observed.terminal !== beforeCallable.terminal) {
throw new Error(`Callable execution leaked or skipped a request: ${JSON.stringify({beforeCallable, observed})}`)
}
const plainPacket = {
method: 'POST',
url: new URL(requestPath, targetURL).toString(),
headers: [{name: 'Content-Type', value: 'application/json'}],
bodyBase64: Buffer.from(JSON.stringify(plaintext)).toString('base64'),
}
const validation = await extensionRequest(controlPage, 'analysis.profile.validate', {
tabId,
frameId: 0,
candidateId: candidate.id,
callableId: callable.id,
inputPaths: ['body'],
name: '在线取钥明文网关',
packet: plainPacket,
comparisonMode: 'structure',
})
if (!validation?.valid || !validation?.saveEligible || !validation.validationDraft?.id) {
throw new Error(`Online-key Profile validation failed: ${JSON.stringify({
valid: validation?.valid,
saveEligible: validation?.saveEligible,
proofLevel: validation?.proofLevel,
})}`)
}
const validationDraft = await extensionRequest(
controlPage,
'analysis.profile.validation.latest',
{tabId, frameId: 0},
)
if (!validationDraft?.profile || validationDraft.id !== validation.validationDraft.id) {
throw new Error('Validated online-key Profile draft was not available for confirmation')
}
const savedProfile = await extensionRequest(
controlPage,
'transform.profile.save',
validationDraft.profile,
)
if (
savedProfile.requestTransaction?.callableId !== callable.id
|| savedProfile.requestTransaction?.transaction?.version !== 2
) {
throw new Error('Saved Profile did not retain its trusted request-transaction binding')
}
const beforeProfile = {...observed}
const execution = await extensionRequest(controlPage, 'transform.execute', {
profileId: savedProfile.id,
direction: 'request',
packet: plainPacket,
})
if (observed.key !== beforeProfile.key + 1 || observed.terminal !== beforeProfile.terminal) {
throw new Error(`Saved Profile leaked or skipped a request: ${JSON.stringify({beforeProfile, observed})}`)
}
const sessionHeader = execution.setHeaders?.find((header) => header.name.toLowerCase() === 'cookie')
if (!sessionHeader?.value.includes('PHPSESSID=')) {
throw new Error(`Saved Profile did not bind the browser session to the outgoing packet: ${JSON.stringify(execution)}`)
}
const wireBody = JSON.parse(Buffer.from(execution.bodyBase64, 'base64').toString('utf8'))
if (typeof wireBody.encryptedData !== 'string' || Object.keys(wireBody).length !== 1) {
throw new Error(`Saved Profile produced an invalid terminal envelope: ${JSON.stringify(wireBody)}`)
}
const response = await fetch(
execution.url,
transformedFetchOptions(execution, plainPacket.headers),
)
const result = await response.json()
if (!result.success) {
throw new Error(`Target server rejected the session-bound Profile output: ${JSON.stringify(result)}`)
}
process.stdout.write(
'Online-key transaction verified: evidence inferred one bounded prerequisite, the browser sent no terminal request during replay, the saved Profile exported its browser session, and the target server accepted the final packet.\n',
)
} finally {
await harness?.close()
}
+147
View File
@@ -0,0 +1,147 @@
import {
extensionRequest,
launchBrowserAgentContractHarness,
transformedFetchOptions,
waitFor,
} from './browser-agent-contract-harness.mjs'
const targetURL = process.env.DES_TARGET || 'http://127.0.0.1:82/'
const requestPath = '/encrypt/des.php'
const plaintext = {username: 'admin', password: '123456'}
let harness
function pathOf(url) {
return new URL(url, targetURL).pathname
}
async function performLogin(targetPage, password = 'wrong-password') {
await targetPage.locator('#username').fill('admin')
await targetPage.locator('#password').fill(password)
await targetPage.getByRole('button', {name: '登录', exact: true}).click()
return targetPage.getByRole('button', {name: 'Des规律Key', exact: true})
}
try {
harness = await launchBrowserAgentContractHarness({
profilePrefix: 'yakit-des-',
targetURL,
})
const {controlPage, tabId, targetPage} = harness
let browserRequestCount = 0
targetPage.on('request', (request) => {
if (pathOf(request.url()) === requestPath) browserRequestCount += 1
})
await extensionRequest(controlPage, 'recording.start', {
tabId, frameId: 0, captureValues: true, maxEntries: 120, maxValueBytes: 8_192,
})
await (await performLogin(targetPage)).click()
await targetPage.waitForTimeout(500)
if (browserRequestCount !== 1) throw new Error(`Initial DES recording expected one request, received ${browserRequestCount}`)
const snapshot = await extensionRequest(controlPage, 'recording.get', {tabId, frameId: 0, limit: 120})
const candidate = snapshot.profileCandidates?.find((item) => pathOf(item.request?.url || '') === requestPath)
if (!candidate || candidate.status !== 'capture-required') {
throw new Error(`Structured DES output was not routed through business-envelope capture: ${JSON.stringify(candidate)}`)
}
const matcherEvent = snapshot.events?.find((event) => event.id === candidate.capturePlan?.matcherEventId)
if (matcherEvent?.crypto?.family !== 'symmetric'
|| !matcherEvent.crypto.operation.toLowerCase().includes('des')
|| !matcherEvent.wrapperHandleId) {
throw new Error(`DES candidate has no reusable crypto matcher: ${JSON.stringify(matcherEvent)}`)
}
await extensionRequest(controlPage, 'deep.capture.start', {
tabId,
frameId: 0,
matcher: {
kind: 'crypto',
adapterId: matcherEvent.crypto.adapterId,
operation: matcherEvent.crypto.operation,
wrapperHandleId: matcherEvent.wrapperHandleId,
scriptUrl: matcherEvent.scriptUrl,
frameHints: candidate.capturePlan.frameHints,
},
})
const replayButton = await performLogin(targetPage)
let replayFailure
const replay = replayButton.click({noWaitAfter: true, timeout: 20_000})
.catch((reason) => { replayFailure = reason })
const paused = await waitFor(controlPage, 'deep.capture.status', {tabId, frameId: 0}, (value) => (
value?.state === 'paused' && value.pause?.collecting !== true
), 20_000)
const automatic = paused.pause?.automaticCapture
const frame = paused.pause?.frames?.find((item) => item.id === automatic?.frameId)
if (automatic?.state !== 'ready' || automatic.strategy !== 'request-transaction'
|| frame?.functionName !== 'encryptAndSendDataDES') {
throw new Error(`DES deep capture did not select its business request envelope: ${JSON.stringify({automatic, frame})}`)
}
const callable = await extensionRequest(controlPage, 'callable.create', {
tabId,
frameId: 0,
source: 'deep-capture',
strategy: 'request-transaction',
callFrameId: frame.id,
name: 'DES 请求事务',
candidateId: candidate.id,
})
await replay
if (replayFailure) throw replayFailure
await targetPage.waitForTimeout(250)
if (browserRequestCount !== 1) throw new Error(`DES capture leaked a terminal request; observed ${browserRequestCount}`)
const callableExecution = await extensionRequest(controlPage, 'callable.execute', {
tabId, frameId: 0, callableId: callable.id, args: [plaintext],
})
if (callableExecution.value?.username !== plaintext.username
|| !/^[a-f0-9]+$/i.test(callableExecution.value?.password || '')) {
throw new Error(`DES request transaction did not preserve the Hex envelope: ${JSON.stringify(callableExecution)}`)
}
const plainPacket = {
method: 'POST',
url: new URL(requestPath, targetURL).toString(),
headers: [{name: 'Content-Type', value: 'application/json'}],
bodyBase64: Buffer.from(JSON.stringify(plaintext)).toString('base64'),
}
const validation = await extensionRequest(controlPage, 'analysis.profile.validate', {
tabId,
frameId: 0,
candidateId: candidate.id,
callableId: callable.id,
inputPaths: ['body'],
name: 'DES 明文网关',
packet: plainPacket,
comparisonMode: 'structure',
})
if (!validation?.valid || !validation?.saveEligible || !validation.validationDraft?.id) {
throw new Error(`DES Profile validation failed: ${JSON.stringify(validation)}`)
}
const validationDraft = await extensionRequest(controlPage, 'analysis.profile.validation.latest', {tabId, frameId: 0})
const savedProfile = await extensionRequest(controlPage, 'transform.profile.save', validationDraft.profile)
const explanationText = JSON.stringify(savedProfile.explanation)
if (!explanationText.includes('DES') || explanationText.includes(plaintext.password)) {
throw new Error(`DES semantic explanation is missing or persisted plaintext: ${explanationText}`)
}
const execution = await extensionRequest(controlPage, 'transform.execute', {
profileId: savedProfile.id,
direction: 'request',
packet: plainPacket,
})
const wireBody = JSON.parse(Buffer.from(execution.bodyBase64, 'base64').toString('utf8'))
if (wireBody.username !== plaintext.username || !/^[a-f0-9]+$/i.test(wireBody.password || '')) {
throw new Error(`Saved DES Profile produced an invalid terminal body: ${JSON.stringify(wireBody)}`)
}
if (!execution.nodeTrace?.length || !execution.fieldChanges?.some((change) => change.path === 'body.password')) {
throw new Error(`Saved DES Profile did not return an explainable runtime trace: ${JSON.stringify(execution)}`)
}
if (browserRequestCount !== 1) throw new Error(`DES Profile execution leaked a browser request; observed ${browserRequestCount}`)
const response = await fetch(execution.url, transformedFetchOptions(execution, plainPacket.headers))
const result = await response.json()
if (!result.success) throw new Error(`Target server rejected the saved DES Profile output: ${JSON.stringify(result)}`)
process.stdout.write('DES transaction verified: the structured CipherParams result required business-envelope capture, replay preserved Hex serialization, runtime evidence stayed value-free, and the target accepted the final packet.\n')
} finally {
await harness?.close()
}
+374 -78
View File
@@ -52,6 +52,7 @@ const closureFunctionName = `build_${closureHoldoutSeed}`;
const closureSenderName = `send_${closureHoldoutSeed}`;
const closureInitialMarker = `module-recording-${closureHoldoutSeed}`;
const closureReplayMarker = `module-replay-${closureHoldoutSeed}`;
const closureProfileMarker = `module-profile-${closureHoldoutSeed}`;
const WASM_XOR_MASK = 23;
function decryptRSALabValue(value) {
@@ -441,6 +442,7 @@ function clientAuthPayload(origin, challenge, auth) {
return [
'yak-browser-bridge-v3', 'client-auth', origin, engineIdentityId, engineInstanceId, challenge,
auth.installationId || '', auth.client || '', auth.version || '', [...(auth.capabilities || [])].sort().join(','),
String(auth.capabilityCatalog?.version || ''), auth.capabilityCatalog?.hash || '',
auth.taskId || '', auth.grantId || '', auth.resumeSessionId || '',
].join('\n');
}
@@ -597,9 +599,35 @@ async function callBridge(socket, id, method, params) {
return await response;
}
function recordingDiagnostic(response) {
const result = response?.result || response?.data;
return JSON.stringify({
errorCode: response?.error?.code,
status: result?.status ? {
active: result.status.active,
documentAvailable: result.status.documentAvailable,
count: result.status.count,
droppedCount: result.status.droppedCount,
} : undefined,
eventKinds: result?.events?.slice(0, 64).map((event) => ({
kind: event.kind,
operation: event.operation,
adapterId: event.crypto?.adapterId || event.transform?.adapterId,
})),
candidates: result?.profileCandidates?.slice(0, 24).map((candidate) => ({
id: candidate.id,
status: candidate.status,
destination: candidate.request?.destination,
serialization: candidate.request?.serialization,
adapterId: candidate.source?.crypto?.adapterId,
missing: candidate.missing?.map((item) => item.kind),
})),
});
}
const browserErrors = [];
let context;
try {
agentContractRun: try {
context = await chromium.launchPersistentContext(userDataDir, {
executablePath,
headless: true,
@@ -656,7 +684,41 @@ try {
const popup = await context.newPage();
await popup.setViewportSize({ width: 390, height: 600 });
await popup.goto(`chrome-extension://${extensionId}/popup.html`);
await popup.locator('.popup-shell').waitFor();
try {
await popup.locator('.popup-shell').waitFor({ timeout: 8_000 });
} catch {
const popupText = await popup.locator('body').innerText().catch(() => '');
const actionStatus = await popup.evaluate(async () => {
const inspect = async (action) => Promise.race([
chrome.runtime.sendMessage({ action }).then(
(response) => response?.ok ? 'ok' : `error:${response?.error || 'unknown'}`,
(error) => `rejected:${error instanceof Error ? error.message : String(error)}`,
),
new Promise((resolveStatus) => setTimeout(() => resolveStatus('timeout'), 1_000)),
]);
return Object.fromEntries(await Promise.all(
['state.get', 'tab.active', 'bridge.status'].map(async (action) => [action, await inspect(action)]),
));
});
const workerDiagnostic = await serviceWorker.evaluate(async () => ({
metrics: (await chrome.storage.local.get('runtime.metrics.v1'))['runtime.metrics.v1'] || null,
manifestVersion: chrome.runtime.getManifest().version,
})).catch((error) => ({ error: error instanceof Error ? error.message : String(error) }));
const diagnosticsPage = await context.newPage();
await diagnosticsPage.goto(`chrome://extensions/?id=${extensionId}`);
const extensionDiagnostic = await diagnosticsPage.evaluate(() => {
const collect = (root) => {
let text = root.textContent || '';
for (const element of root.querySelectorAll('*')) {
if (element.shadowRoot) text += ` ${collect(element.shadowRoot)}`;
}
return text;
};
return collect(document).replace(/\s+/g, ' ').trim().slice(0, 1_200);
});
await diagnosticsPage.close();
throw new Error(`Popup did not finish loading: ${popupText.slice(0, 200)}; ${JSON.stringify(actionStatus)}; ${JSON.stringify(workerDiagnostic)}; ${extensionDiagnostic}; ${browserErrors.slice(-3).join(' | ')}`);
}
await popup.getByText('Authenticated Security Console', { exact: true }).waitFor();
const popupBrandsLoaded = await popup.locator('.yak-mark, .yakit-mark').evaluateAll((images) => images.every((image) => image instanceof HTMLImageElement && image.complete && image.naturalWidth > 0));
if (!popupBrandsLoaded) throw new Error('Popup brand assets did not load');
@@ -874,26 +936,26 @@ try {
await testCookie.click();
if (await options.locator('.rule-editor input').first().inputValue() !== 'yakit_e2e_session') throw new Error('Cookie Editor did not load the selected HttpOnly cookie');
if (await options.getByText('HttpOnly', { exact: true }).count() === 0) throw new Error('Cookie Editor did not expose HttpOnly metadata');
const cookieTransferChecks = await options.evaluate(async ({ url }) => {
const cookieTransferChecks = await options.evaluate(async ({ url, tabId }) => {
const send = async (action, payload) => {
const response = await chrome.runtime.sendMessage({ action, payload });
if (!response?.ok) throw new Error(response?.error || action);
return response.data;
};
const redacted = await send('cookie.export', { url, format: 'set-cookie', includeValues: false });
const sensitive = await send('cookie.export', { url, format: 'json', includeValues: true });
const redacted = await send('cookie.export', { url, tabId, format: 'set-cookie', includeValues: false });
const sensitive = await send('cookie.export', { url, tabId, format: 'json', includeValues: true });
const imports = [];
imports.push(await send('cookie.import', { url, format: 'json', text: JSON.stringify([{ name: 'json_import', value: 'json-value', path: '/' }]) }));
imports.push(await send('cookie.import', { url, format: 'netscape', text: '127.0.0.1\tFALSE\t/\tFALSE\t0\tnetscape_import\tnetscape-value\n' }));
imports.push(await send('cookie.import', { url, format: 'set-cookie', text: 'Set-Cookie: raw_import=raw-value; Path=/; HttpOnly; SameSite=Lax; Priority=High' }));
const listed = await send('cookie.list', { url });
imports.push(await send('cookie.import', { url, tabId, format: 'json', text: JSON.stringify([{ name: 'json_import', value: 'json-value', path: '/' }]) }));
imports.push(await send('cookie.import', { url, tabId, format: 'netscape', text: '127.0.0.1\tFALSE\t/\tFALSE\t0\tnetscape_import\tnetscape-value\n' }));
imports.push(await send('cookie.import', { url, tabId, format: 'set-cookie', text: 'Set-Cookie: raw_import=raw-value; Path=/; HttpOnly; SameSite=Lax; Priority=High' }));
const listed = await send('cookie.list', { url, tabId });
const imported = listed.filter((cookie) => ['json_import', 'netscape_import', 'raw_import'].includes(cookie.name));
const removed = await send('cookie.removeMany', { cookies: imported.map((cookie) => ({
url: `${cookie.secure ? 'https' : 'http'}://${cookie.domain.replace(/^\./, '')}${cookie.path}`,
name: cookie.name, storeId: cookie.storeId, partitionKey: cookie.partitionKey,
})) });
return { redacted, sensitive, imports, imported: imported.map((cookie) => cookie.name), removed };
}, { url: testUrl });
}, { url: testUrl, tabId: targetTab.id });
if (!cookieTransferChecks.redacted.includes('[REDACTED]') || cookieTransferChecks.redacted.includes('authenticated')) throw new Error(`Cookie export was not redacted: ${cookieTransferChecks.redacted}`);
if (!cookieTransferChecks.sensitive.includes('authenticated')) throw new Error('Explicit Cookie value export omitted values');
if (cookieTransferChecks.imported.length !== 3 || cookieTransferChecks.removed.removed !== 3 || cookieTransferChecks.imports[2].warnings.length === 0) {
@@ -994,10 +1056,6 @@ try {
if (!optionsTab?.id || !floatingFrame) throw new Error('Could not resolve floating frame sender boundary');
await floatingFrame.locator('.floating-panel--embedded .floating-panel__body').waitFor({ state: 'visible', timeout: 10_000 });
await floatingFrame.getByText('快速切换', { exact: true }).waitFor({ state: 'visible' });
const floatingBrandLoaded = await floatingFrame.locator('.floating-panel__brand img').evaluate((image) => (
image instanceof HTMLImageElement && image.complete && image.naturalWidth > 0
));
if (!floatingBrandLoaded) throw new Error('Expanded floating panel Yak asset did not load');
await floatingFrame.evaluate(async () => {
await new Promise((resolveFrame) => requestAnimationFrame(() => requestAnimationFrame(resolveFrame)));
});
@@ -1061,7 +1119,9 @@ try {
});
}, { endpoint: bridgeEndpoint, tabId: targetTab.id });
const { socket: bridgeSocket, hello } = await bridgeConnection;
if (hello.type !== 'auth' || hello.client !== 'yakit-browser-extension' || hello.protocolVersion !== 3 || !hello.installationId || !hello.signature || !hello.capabilities?.includes('browser.eval')) {
if (hello.type !== 'auth' || hello.client !== 'yakit-browser-extension' || hello.protocolVersion !== 3
|| !hello.installationId || !hello.signature || !hello.capabilities?.includes('browser.eval')
|| hello.capabilityCatalog?.version !== 1 || !hello.capabilityCatalog?.hash) {
throw new Error(`Unexpected Bridge hello: ${JSON.stringify(hello)}`);
}
await options.getByRole('button', { name: '引擎连接' }).click();
@@ -1305,7 +1365,7 @@ try {
const recordingSnapshot = await callBridge(bridgeSocket, 'verify-recording-get', 'browser.recording.get', { tabId: targetTab.id, limit: 200 });
const observedKinds = new Set(recordingSnapshot.result?.events?.map((item) => item.kind));
for (const kind of ['fetch', 'xhr', 'form', 'beacon', 'worker', 'message', 'websocket', 'crypto']) {
if (!observedKinds.has(kind)) throw new Error(`Browser recording missed ${kind}: ${JSON.stringify(recordingSnapshot)}`);
if (!observedKinds.has(kind)) throw new Error(`Browser recording missed ${kind}: ${recordingDiagnostic(recordingSnapshot)}`);
}
const workerSendEvent = recordingSnapshot.result?.events?.find((item) => item.kind === 'worker' && item.operation === 'worker.postMessage');
const workerReceiveEvent = recordingSnapshot.result?.events?.find((item) => item.kind === 'worker' && item.operation === 'worker.message');
@@ -1313,38 +1373,38 @@ try {
if (!workerSendEvent?.wrapperHandleId || !workerReceiveEvent || workerSendEvent.channelId !== workerReceiveEvent.channelId
|| workerSendEvent.traceId !== workerReceiveEvent.traceId
|| !recordingSnapshot.result?.links?.some((item) => item.kind === 'channel' && item.fromEventId === workerSendEvent.id && item.toEventId === workerReceiveEvent.id)) {
throw new Error(`Worker boundary did not retain its exact handle and async Trace correlation: ${JSON.stringify(recordingSnapshot)}`);
throw new Error(`Worker boundary did not retain its exact handle and async Trace correlation: ${recordingDiagnostic(recordingSnapshot)}`);
}
const unknownBoundaryCandidate = recordingSnapshot.result?.profileCandidates?.find((candidate) => (
candidate.request.eventId === workerRequestEvent?.id && candidate.source.operation === 'unknown-business-envelope'
));
if (!unknownBoundaryCandidate || unknownBoundaryCandidate.status !== 'capture-required') {
throw new Error(`Unknown Worker/ESM boundary did not remain actionable: ${JSON.stringify(recordingSnapshot)}`);
throw new Error(`Unknown Worker/ESM boundary did not remain actionable: ${recordingDiagnostic(recordingSnapshot)}`);
}
const observedCryptoProviders = new Set(recordingSnapshot.result?.events
?.filter((item) => item.kind === 'crypto')
.map((item) => item.crypto?.adapterId));
for (const provider of ['webcrypto', 'cryptojs', 'jsencrypt', 'sm-crypto', 'node-forge']) {
if (!observedCryptoProviders.has(provider)) {
throw new Error(`Browser recording missed the ${provider} crypto adapter: ${JSON.stringify(recordingSnapshot)}`);
throw new Error(`Browser recording missed the ${provider} crypto adapter: ${recordingDiagnostic(recordingSnapshot)}`);
}
}
const smOperations = new Set(recordingSnapshot.result?.events
?.filter((item) => item.kind === 'crypto' && item.crypto?.adapterId === 'sm-crypto')
.map((item) => item.crypto?.operation));
for (const operation of ['sm2.encrypt', 'sm2.decrypt', 'sm2.sign', 'sm2.verify', 'sm3.digest', 'sm4.encrypt', 'sm4.decrypt']) {
if (!smOperations.has(operation)) throw new Error(`Real sm-crypto fixture missed ${operation}: ${JSON.stringify(recordingSnapshot)}`);
if (!smOperations.has(operation)) throw new Error(`Real sm-crypto fixture missed ${operation}: ${recordingDiagnostic(recordingSnapshot)}`);
}
const forgeEvents = recordingSnapshot.result?.events
?.filter((item) => item.kind === 'crypto' && item.crypto?.adapterId === 'node-forge') || [];
for (const phase of ['create', 'init', 'update', 'final']) {
if (!forgeEvents.some((item) => item.crypto?.state?.phase === phase && item.crypto?.state?.correlationId)) {
throw new Error(`Real node-forge fixture missed correlated ${phase} state: ${JSON.stringify(recordingSnapshot)}`);
throw new Error(`Real node-forge fixture missed correlated ${phase} state: ${recordingDiagnostic(recordingSnapshot)}`);
}
}
if (!forgeEvents.some((item) => item.crypto?.operation === 'rsa.encrypt' && item.callHandleId && item.callableCapable)
|| !forgeEvents.some((item) => item.crypto?.operation === 'cipher.encrypt.output.toHex')) {
throw new Error(`Real node-forge fixture missed RSA callable or cipher output boundary: ${JSON.stringify(recordingSnapshot)}`);
throw new Error(`Real node-forge fixture missed RSA callable or cipher output boundary: ${recordingDiagnostic(recordingSnapshot)}`);
}
const linkedCryptoEvent = recordingSnapshot.result?.events?.find((item) => (
item.kind === 'crypto' && item.crypto?.adapterId === 'cryptojs' && item.crypto?.operation === 'SHA256'
@@ -1360,6 +1420,9 @@ try {
const closureRequestEvent = recordingSnapshot.result?.events?.find((item) => (
item.kind === 'fetch' && item.url?.includes(closureSubmitPath)
));
const closureCandidate = recordingSnapshot.result?.profileCandidates?.find((candidate) => (
candidate.request?.eventId === closureRequestEvent?.id
));
const webCryptoDigestEvents = recordingSnapshot.result?.events?.filter((item) => (
item.kind === 'crypto' && item.crypto?.adapterId === 'webcrypto'
&& item.crypto?.operation === 'digest' && item.wrapperHandleId
@@ -1398,21 +1461,33 @@ try {
.map((item) => `${item.crypto?.adapterId}:${item.crypto?.operation}:${Boolean(item.wrapperHandleId)}`),
})}`);
}
if (!closureRequestEvent || !closureDigestLink) {
if (!closureRequestEvent || !closureDigestLink || !closureCandidate) {
throw new Error(`Randomized ESM/WASM holdout did not use the generic evidence graph: ${JSON.stringify({
closureSubmitPath,
closureDigestField,
request: closureRequestEvent,
candidate: closureCandidate,
digests: webCryptoDigestEvents,
links: recordingSnapshot.result?.links?.filter((item) => item.toEventId === closureRequestEvent?.id),
})}`);
}
const registeredTraces = await callBridge(
bridgeSocket,
'verify-closure-agent-trace-list',
'browser.recording.trace.list',
{ tabId: targetTab.id, limit: 100 },
);
if (registeredTraces.error || !registeredTraces.result?.some((trace) => (
trace.candidates?.some((candidate) => candidate.id === closureCandidate.id)
))) {
throw new Error(`Randomized ESM/WASM candidate was not registered by the Agent trace contract: ${JSON.stringify(registeredTraces)}`);
}
const linkedFetchEvent = recordingSnapshot.result?.events?.find((item) => item.kind === 'fetch' && item.url?.includes('recorder-linked-fetch'));
if (!linkedCryptoEvent || !linkedFetchEvent || !recordingSnapshot.result?.links?.some((link) => link.fromEventId === linkedCryptoEvent.id && link.toEventId === linkedFetchEvent.id)) {
throw new Error(`Recording did not link CryptoJS output to Fetch input: ${JSON.stringify(recordingSnapshot)}`);
throw new Error(`Recording did not link CryptoJS output to Fetch input: ${recordingDiagnostic(recordingSnapshot)}`);
}
if (!recordingSnapshot.result?.traces?.some((trace) => trace.eventIds.includes(linkedCryptoEvent.id) && trace.eventIds.includes(linkedFetchEvent.id))) {
throw new Error(`Linked events were not grouped into one business Trace: ${JSON.stringify(recordingSnapshot)}`);
throw new Error(`Linked events were not grouped into one business Trace: ${recordingDiagnostic(recordingSnapshot)}`);
}
const inferredProfile = recordingSnapshot.result?.profileCandidates?.find((candidate) => (
candidate.source?.eventId === linkedCryptoEvent.id && candidate.request?.eventId === linkedFetchEvent.id
@@ -1423,19 +1498,30 @@ try {
|| inferredProfile.confidence?.level !== 'high'
|| inferredProfile.source?.arguments?.[0]?.role !== 'data'
|| inferredProfile.aiContext?.valuePolicy !== 'metadata-only') {
throw new Error(`Recording did not infer a safe high-confidence Profile candidate: ${JSON.stringify(recordingSnapshot)}`);
throw new Error(`Recording did not infer a safe high-confidence Profile candidate: ${recordingDiagnostic(recordingSnapshot)}`);
}
const formLinkedFetchEvent = recordingSnapshot.result?.events?.find((item) => item.kind === 'fetch' && item.url?.includes('recorder-form-linked-fetch'));
const formFieldLink = recordingSnapshot.result?.links?.find((link) => (
link.toEventId === formLinkedFetchEvent?.id && link.toPath === '$body:form.encryptedData'
));
const formLinkedCandidate = recordingSnapshot.result?.profileCandidates?.find((candidate) => (
candidate.source?.eventId === formFieldLink?.fromEventId && candidate.request?.eventId === formLinkedFetchEvent?.id
candidate.request?.eventId === formLinkedFetchEvent?.id
&& candidate.request?.destination === 'body.encryptedData'
));
if (!formLinkedFetchEvent || !formFieldLink || formLinkedCandidate?.request?.destination !== 'body.encryptedData'
|| formLinkedCandidate?.request?.serialization !== 'form-field'
|| formLinkedCandidate?.status !== 'ready' || formLinkedCandidate?.confidence?.level !== 'high') {
throw new Error(`Recording did not preserve the generic form field value chain: ${JSON.stringify(recordingSnapshot)}`);
|| formLinkedCandidate?.status !== 'capture-required' || formLinkedCandidate?.confidence?.level !== 'high') {
throw new Error(`Recording did not preserve the generic form field value chain: ${JSON.stringify({
formLinkedFetchEvent,
formFieldLink,
formLinkedCandidate,
relatedCandidates: recordingSnapshot.result?.profileCandidates?.filter((candidate) => (
candidate.request?.eventId === formLinkedFetchEvent?.id
)),
relatedLinks: recordingSnapshot.result?.links?.filter((link) => (
link.toEventId === formLinkedFetchEvent?.id
)),
})}`);
}
const rsaCryptoEvent = recordingSnapshot.result?.events?.find((item) => (
item.kind === 'crypto' && item.crypto?.adapterId === 'jsencrypt' && item.crypto?.operation === 'encrypt'
@@ -1444,8 +1530,7 @@ try {
item.kind === 'fetch' && item.url?.includes('/encrypt/rsa.php?source=recorder-rsa-profile')
));
const rsaFieldLink = recordingSnapshot.result?.links?.find((link) => (
link.fromEventId === rsaCryptoEvent?.id
&& link.toEventId === rsaRequestEvent?.id
link.toEventId === rsaRequestEvent?.id
&& link.toPath === '$body:form.data'
));
const rsaCandidate = recordingSnapshot.result?.profileCandidates?.find((candidate) => (
@@ -1457,10 +1542,15 @@ try {
|| rsaCryptoEvent.crypto?.key?.kind !== 'public'
|| rsaCryptoEvent.crypto?.key?.bits !== 1024
|| !rsaCryptoEvent.crypto?.key?.fingerprint
|| rsaCandidate?.status !== 'ready'
|| rsaCandidate?.status !== 'capture-required'
|| rsaCandidate.request?.destination !== 'body.data'
|| rsaCandidate.request?.serialization !== 'form-field') {
throw new Error(`JSEncrypt RSA was not inferred as an executable form-field Profile: ${JSON.stringify(recordingSnapshot)}`);
throw new Error(`JSEncrypt RSA did not preserve its form-field evidence through serialization: ${JSON.stringify({
rsaCryptoEvent,
rsaRequestEvent,
rsaFieldLink,
rsaCandidate,
})}`);
}
const rsaAIContext = JSON.stringify(rsaCandidate.aiContext);
if (rsaAIContext.includes('BEGIN PUBLIC KEY') || rsaAIContext.includes(rsaLabPublicModulusHex)) {
@@ -1478,7 +1568,7 @@ try {
captureValues: true,
});
if (!deniedSensitiveRecording.error?.message?.includes('browser.recording.sensitive.read')) {
throw new Error(`Recording value capture did not require its sensitive scope: ${JSON.stringify(deniedSensitiveRecording)}`);
throw new Error(`Recording value capture did not require its sensitive scope: ${recordingDiagnostic(deniedSensitiveRecording)}`);
}
await callBridge(bridgeSocket, 'verify-recording-stop-metadata', 'browser.recording.stop', { tabId: targetTab.id });
await options.evaluate(async ({ tabId, frameIds }) => {
@@ -1493,6 +1583,7 @@ try {
'browser.recording.read', 'browser.recording.control', 'browser.recording.sensitive.read', 'browser.callable.execute',
'browser.debugger.read', 'browser.debugger.control',
'browser.transform.read', 'browser.transform.manage', 'browser.transform.execute',
'browser.isolation.read',
'browser.proxy.read', 'browser.proxy.write',
],
durationMinutes: 5,
@@ -1517,7 +1608,7 @@ try {
});
const sensitiveRecording = await callBridge(bridgeSocket, 'verify-recording-sensitive-get', 'browser.recording.get', { tabId: targetTab.id });
if (!JSON.stringify(sensitiveRecording.result).includes('recorder-sensitive-preview-686')) {
throw new Error(`Explicit recording value capture did not return its bounded preview: ${JSON.stringify(sensitiveRecording)}`);
throw new Error(`Explicit recording value capture did not return its bounded preview: ${recordingDiagnostic(sensitiveRecording)}`);
}
const callableSource = sensitiveRecording.result?.events?.find((item) => (
item.kind === 'crypto'
@@ -1526,7 +1617,7 @@ try {
&& item.callHandleId
&& item.callableCapable
));
if (!callableSource) throw new Error(`Sensitive recording did not retain an executable JSEncrypt call handle: ${JSON.stringify(sensitiveRecording)}`);
if (!callableSource) throw new Error(`Sensitive recording did not retain an executable JSEncrypt call handle: ${recordingDiagnostic(sensitiveRecording)}`);
const createdCallable = await callBridge(bridgeSocket, 'verify-callable-create', 'browser.callable.create', {
tabId: targetTab.id,
source: 'recording',
@@ -1656,24 +1747,135 @@ try {
throw new Error(`Randomized ESM/WASM callable could not be retained: ${JSON.stringify(closureCallable)}`);
}
await webPage.locator('#opaque-module-result').getByText(closureReplayMarker, { exact: true }).waitFor();
const closureReplay = await callBridge(bridgeSocket, 'verify-closure-callable-execute', 'browser.callable.execute', {
const closureReplay = await callBridge(bridgeSocket, 'verify-closure-callable-replay', 'browser.callable.replay', {
tabId: targetTab.id,
callableId: closureCallable.result.id,
args: [{ marker: closureReplayMarker, nested: { seed: closureHoldoutSeed } }],
});
if (closureReplay.error || !closureReplay.result?.value || typeof closureReplay.result.value !== 'object') {
if (closureReplay.error || !closureReplay.result?.execution?.value
|| typeof closureReplay.result.execution.value !== 'object') {
throw new Error(`Randomized ESM/WASM callable replay failed: ${JSON.stringify(closureReplay)}`);
}
const closureServerResponse = await fetch(new URL(closureSubmitPath, testUrl), {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: JSON.stringify(closureReplay.result.value),
body: JSON.stringify(closureReplay.result.execution.value),
});
const closureServerResult = await closureServerResponse.json();
if (!closureServerResponse.ok || !closureServerResult.ok || closureServerResult.marker !== closureReplayMarker) {
throw new Error(`Independent server rejected the retained ESM/WASM callable: ${JSON.stringify(closureServerResult)}`);
}
const closurePlainPacket = {
method: 'POST',
url: new URL(closureSubmitPath, testUrl).toString(),
headers: [{ name: 'Content-Type', value: 'application/json' }],
bodyBase64: Buffer.from(JSON.stringify({
marker: closureProfileMarker,
nested: { seed: closureHoldoutSeed },
})).toString('base64'),
};
const closureProposal = await callBridge(
bridgeSocket,
'verify-closure-agent-profile-propose',
'browser.profile.propose',
{
tabId: targetTab.id,
candidateId: closureCandidate.id,
callableId: closureCallable.result.id,
inputPaths: ['body'],
name: 'Opaque ESM/WASM Agent contract',
},
);
if (closureProposal.error
|| closureProposal.result?.proposal?.compiler !== 'browser-transform-guided-v1') {
throw new Error(`Randomized ESM/WASM Profile proposal failed: ${JSON.stringify(closureProposal)}`);
}
const closureValidation = await callBridge(
bridgeSocket,
'verify-closure-agent-profile-validate',
'browser.profile.validate',
{
tabId: targetTab.id,
candidateId: closureCandidate.id,
callableId: closureCallable.result.id,
inputPaths: ['body'],
name: 'Opaque ESM/WASM Agent contract',
packet: closurePlainPacket,
comparisonMode: 'structure',
},
);
if (closureValidation.error || !closureValidation.result?.valid
|| closureValidation.result?.validationDraft?.contractVersion !== 1) {
throw new Error(`Randomized ESM/WASM Profile validation failed: ${JSON.stringify(closureValidation)}`);
}
const closureValidationDraft = await callBridge(
bridgeSocket,
'verify-closure-agent-validation-latest',
'browser.profile.validation.latest',
{ tabId: targetTab.id },
);
if (closureValidationDraft.error
|| closureValidationDraft.result?.id !== closureValidation.result.validationDraft.id
|| closureValidationDraft.result?.contractVersion !== 1
|| closureValidationDraft.result?.profile?.id) {
throw new Error(`Randomized ESM/WASM Yakit handoff draft is invalid: ${JSON.stringify(closureValidationDraft)}`);
}
const closureProfile = await callBridge(
bridgeSocket,
'verify-closure-agent-profile-save',
'browser.transform.profile.save',
closureValidationDraft.result.profile,
);
if (closureProfile.error || !closureProfile.result?.id) {
throw new Error(`Randomized ESM/WASM confirmed Profile was not saved: ${JSON.stringify(closureProfile)}`);
}
const closureProfileExecution = await callBridge(
bridgeSocket,
'verify-closure-agent-profile-execute',
'browser.transform.execute',
{
profileId: closureProfile.result.id,
direction: 'request',
packet: closurePlainPacket,
},
);
if (closureProfileExecution.error || !closureProfileExecution.result?.bodyBase64) {
throw new Error(`Randomized ESM/WASM saved Profile did not execute: ${JSON.stringify(closureProfileExecution)}`);
}
const closureProfileServerResponse = await fetch(closureProfileExecution.result.url, {
method: 'POST',
headers: { 'content-type': 'application/json' },
body: Buffer.from(closureProfileExecution.result.bodyBase64, 'base64'),
});
const closureProfileServerResult = await closureProfileServerResponse.json();
if (!closureProfileServerResponse.ok || !closureProfileServerResult.ok
|| closureProfileServerResult.marker !== closureProfileMarker) {
throw new Error(`Independent server rejected the ESM/WASM Profile output: ${JSON.stringify(closureProfileServerResult)}`);
}
const removedClosureProfile = await callBridge(
bridgeSocket,
'verify-closure-agent-profile-delete',
'browser.transform.profile.delete',
{ id: closureProfile.result.id },
);
if (removedClosureProfile.error
|| removedClosureProfile.result?.some((profile) => profile.id === closureProfile.result.id)) {
throw new Error(`Randomized ESM/WASM Agent contract Profile was not removed: ${JSON.stringify(removedClosureProfile)}`);
}
await callBridge(bridgeSocket, 'verify-closure-capture-detach', 'browser.deep_capture.detach', { tabId: targetTab.id });
if (process.env.AGENT_CONTRACT_HOLDOUT_ONLY === '1') {
console.log(JSON.stringify({
verified: 'unknown-esm-wasm-agent-contract',
contractVersion: closureValidationDraft.result.contractVersion,
candidateId: closureCandidate.id,
callableKind: closureCallable.result.kind,
compiler: closureProposal.result.proposal.compiler,
serializationSource: closureProposal.result.proposal.serializationSource,
serverAccepted: closureProfileServerResult.ok,
}, null, 2));
break agentContractRun;
}
await webPage.locator('#crypto-lab[data-ready="true"]').waitFor();
await webPage.locator('#crypto-password').fill('deep-capture-first-901');
@@ -2198,10 +2400,10 @@ try {
const insecureRecording = await options.evaluate(async (tabId) => {
return await chrome.runtime.sendMessage({ action: 'recording.get', payload: { tabId, limit: 100 } });
}, insecureTab.id);
if (!insecureRecording?.ok) throw new Error(`Could not read insecure HTTP recording: ${JSON.stringify(insecureRecording)}`);
if (!insecureRecording?.ok) throw new Error(`Could not read insecure HTTP recording: ${recordingDiagnostic(insecureRecording)}`);
const insecureKinds = new Set(insecureRecording.data.events.map((item) => item.kind));
for (const kind of ['fetch', 'xhr', 'websocket', 'crypto']) {
if (!insecureKinds.has(kind)) throw new Error(`Insecure HTTP recording missed ${kind}: ${JSON.stringify(insecureRecording)}`);
if (!insecureKinds.has(kind)) throw new Error(`Insecure HTTP recording missed ${kind}: ${recordingDiagnostic(insecureRecording)}`);
}
const insecureRecordingStop = await options.evaluate(async (tabId) => {
return await chrome.runtime.sendMessage({ action: 'recording.stop', payload: { tabId } });
@@ -2395,7 +2597,7 @@ try {
{ tabId: targetTab.id, captureValues: true, maxEntries: 80 },
);
if (automaticCaptureRecording.error) {
throw new Error(`Could not start the automatic business capture recording: ${JSON.stringify(automaticCaptureRecording)}`);
throw new Error(`Could not start the automatic business capture recording: ${recordingDiagnostic(automaticCaptureRecording)}`);
}
await webPage.locator('#crypto-password').fill('automatic-capture-seed-951');
await webPage.locator('#crypto-submit').click();
@@ -2431,6 +2633,7 @@ try {
await webPage.locator('#crypto-password').fill('automatic-capture-replay-952');
await webPage.locator('#crypto-submit').click();
await options.locator('#gateway-mode-tab[aria-selected="true"]').waitFor({ timeout: 30_000 });
await options.getByRole('button', { name: '配置', exact: true }).click();
const automaticGateway = options.locator('.transform-guide');
await automaticGateway.waitFor();
if (await automaticGateway.getByLabel('输出形态').inputValue() !== 'body') {
@@ -2468,11 +2671,16 @@ try {
|| automaticCallable.operation !== 'buildLoginEnvelope') {
throw new Error(`One-click capture did not retain the complete business callable: ${JSON.stringify(automaticCallables)}`);
}
await options.locator('.transform-editor-actions').getByRole('button', { name: '保存', exact: true }).click();
const saveAutomaticPipeline = options.locator('.transform-editor-actions').getByRole('button', { name: '保存', exact: true });
if (await saveAutomaticPipeline.isEnabled()) await saveAutomaticPipeline.click();
const executeAutomaticPipeline = options.getByRole('button', { name: '执行 Pipeline', exact: true });
await executeAutomaticPipeline.waitFor({ state: 'visible' });
if (!await executeAutomaticPipeline.isEnabled()) {
throw new Error('Automatic business gateway was neither persisted nor ready for local replay');
}
await executeAutomaticPipeline.click();
await options.getByText('转换完成', { exact: true }).waitFor();
await options.locator('.transform-test-debug').getByText('调试输出', { exact: true }).click();
const automaticLogicalOutput = JSON.parse(await options.locator('.transform-test-result pre').textContent());
if (!automaticLogicalOutput.body?.ciphertext || !automaticLogicalOutput.body?.signature || !automaticLogicalOutput.body?.iv) {
throw new Error(`Automatic business gateway did not execute the complete page closure: ${JSON.stringify(automaticLogicalOutput)}`);
@@ -2530,20 +2738,24 @@ try {
limit: 40,
});
const guidedFormCandidate = guidedFormRecording.result?.profileCandidates?.find((candidate) => (
candidate.status === 'ready'
candidate.status === 'capture-required'
&& candidate.request?.serialization === 'form-field'
&& candidate.request?.destination === 'body.data'
&& candidate.source?.crypto?.adapterId === 'jsencrypt'
));
if (!guidedFormCandidate?.source?.eventId) {
throw new Error(`Dedicated recording did not produce a guided form candidate: ${JSON.stringify(guidedFormRecording)}`);
throw new Error(`Dedicated recording did not produce a capture-ready form candidate: ${recordingDiagnostic(guidedFormRecording)}`);
}
await options.getByRole('button', { name: '网络活动' }).click();
await options.locator('#gateway-mode-tab').click();
const transformWorkbench = options.locator('.transform-workbench');
await transformWorkbench.waitFor();
await options.getByText('E2E login plaintext gateway', { exact: true }).waitFor();
const e2eTransformProfile = options.getByText('E2E login plaintext gateway', { exact: true });
await e2eTransformProfile.waitFor();
await e2eTransformProfile.click();
await options.locator('.transform-data-flow').waitFor();
await options.getByText('明文如何成为线上请求', { exact: true }).waitFor();
const transformBounds = await transformWorkbench.evaluate((element) => ({
clientWidth: element.clientWidth,
scrollWidth: element.scrollWidth,
@@ -2597,7 +2809,7 @@ try {
if (guidedCallableForDelete.crypto?.adapterId !== 'jsencrypt'
|| guidedCallableForDelete.crypto?.key?.bits !== 1024
|| guidedCallableForDelete.crypto?.padding !== 'PKCS1-v1_5') {
throw new Error(`The UI-created callable lost its RSA adapter metadata: ${JSON.stringify(guidedCallableForDelete)}`);
throw new Error('The UI-created callable lost its RSA adapter metadata');
}
const guidedRsaReplay = await callBridge(bridgeSocket, 'verify-guided-rsa-replay', 'browser.callable.execute', {
tabId: targetTab.id,
@@ -2611,36 +2823,26 @@ try {
if (guidedRsaReplay.error
|| guidedRsaReplayPayload?.username !== 'guided-rsa-admin'
|| guidedRsaReplayPayload?.password !== 'guided-rsa-password') {
throw new Error(`The UI-created RSA callable could not replay through its retained receiver: ${JSON.stringify({ guidedRsaReplay, guidedRsaReplayPlaintext, guidedRsaReplayPayload })}`);
throw new Error(`The UI-created RSA callable replay failed: ${guidedRsaReplay.error?.code || 'result-mismatch'}`);
}
await formInferencePanel.getByRole('button', { name: '生成明文网关', exact: true }).click();
const guidedGateway = options.locator('.transform-guide');
await guidedGateway.waitFor();
if (await guidedGateway.getByLabel('输出形态').inputValue() !== 'form-field'
|| await guidedGateway.getByLabel('表单字段名').inputValue() !== 'data') {
throw new Error('Form evidence was not compiled into a guided form-field gateway');
await formInferencePanel.getByRole(
'button',
{ name: '自动捕获完整加密流程', exact: true },
).waitFor();
if (await formInferencePanel.getByRole(
'button',
{ name: '生成明文网关', exact: true },
).count()) {
throw new Error('A low-level RSA callable bypassed the required business-capture step');
}
if (await options.getByLabel('URL 模式').inputValue() !== '*/encrypt/rsa.php'
|| !await options.getByLabel('回放请求 URL').inputValue().then((value) => value.startsWith(`${new URL(testUrl).origin}/encrypt/rsa.php?`))) {
throw new Error('Relative request evidence was not resolved against the current page');
}
await guidedGateway.getByText('自动设置表单 Content-Type', { exact: true }).waitFor();
if (await options.locator('.transform-node-list').isVisible()) {
throw new Error('Guided gateway leaked the advanced DAG editor');
}
const replayBody = await options.getByLabel('回放 Body').inputValue();
if (JSON.stringify(JSON.parse(replayBody)) !== JSON.stringify({ username: 'admin', password: '123456' })) {
throw new Error(`Recorded short sample was not carried into local replay: ${replayBody}`);
}
await options.locator('.transform-test-field-label').getByText('短时样本', { exact: true }).waitFor();
await options.screenshot({ path: resolve(artifacts, 'options-browser-transform-guided-rsa.png') });
await options.locator('.transform-callable-menu > summary').click();
const disposableCallable = options.locator('.transform-callable-list section').filter({ hasText: guidedCallableForDelete.name });
await disposableCallable.getByRole('button', { name: `删除 ${guidedCallableForDelete.name}`, exact: true }).click();
await disposableCallable.getByRole('button', { name: '确认删除', exact: true }).click();
await disposableCallable.waitFor({ state: 'detached' });
await options.screenshot({ path: resolve(artifacts, 'options-browser-transform-callables.png') });
await options.locator('#recording-mode-tab').click();
await options.screenshot({ path: resolve(artifacts, 'options-browser-recording-capture-required.png') });
await options.evaluate(async ({ tabId, callableId }) => {
const response = await chrome.runtime.sendMessage({
action: 'callable.delete',
payload: { tabId, callableId },
});
if (!response?.ok) throw new Error(response?.error || 'callable.delete');
}, { tabId: targetTab.id, callableId: guidedCallableForDelete.id });
await options.locator('#deep-mode-tab').click();
const deepCaptureWorkspace = options.locator('.deep-capture');
await deepCaptureWorkspace.waitFor();
@@ -2681,6 +2883,54 @@ try {
throw new Error(`Agent network capture did not include the explicitly granted request: ${JSON.stringify(bridgeNetworkList)}`);
}
const authorizationAttestation = await callBridge(
bridgeSocket,
'verify-authorization-attestation',
'browser.authorization.context.attest',
{ tabId: targetTab.id, frameId: 0 },
);
if (authorizationAttestation.error || !authorizationAttestation.result?.id) {
throw new Error(`Authorization attestation failed: ${JSON.stringify(authorizationAttestation)}`);
}
const authorizationCandidates = await callBridge(
bridgeSocket,
'verify-authorization-candidates',
'browser.authorization.baseline.candidates',
{
tabId: targetTab.id,
frameId: 0,
authContextKind: 'attestation',
authContextId: authorizationAttestation.result.id,
limit: 20,
},
);
const authorizationCandidate = authorizationCandidates.result?.find(
(candidate) => candidate.url?.includes('/api/session') && candidate.eligible,
);
if (authorizationCandidates.error || !authorizationCandidate?.id) {
throw new Error(`Authorization request candidate was unavailable: ${JSON.stringify(authorizationCandidates)}`);
}
const authorizationBaseline = await callBridge(
bridgeSocket,
'verify-authorization-baseline',
'browser.authorization.baseline.capture',
{
tabId: targetTab.id,
frameId: 0,
authContextKind: 'attestation',
authContextId: authorizationAttestation.result.id,
networkRequestId: authorizationCandidate.id,
comparisonKey: randomBytes(32).toString('base64url'),
},
);
const authorizationBaselineJSON = JSON.stringify(authorizationBaseline.result || {});
if (authorizationBaseline.error || !authorizationBaseline.result?.id
|| authorizationBaselineJSON.includes('network-sensitive-e2e-771')
|| authorizationBaselineJSON.includes('request-header-value')
|| authorizationBaselineJSON.includes('yakit_e2e_session=authenticated')) {
throw new Error('Authorization baseline capture failed its redaction contract');
}
await options.getByRole('button', { name: 'Yakit' }).click();
const fuzzerOpenMessage = await webFuzzerOpenRequest;
const fuzzerPacket = Buffer.from(fuzzerOpenMessage.params?.rawRequestBase64 || '', 'base64').toString('utf8');
@@ -2769,7 +3019,8 @@ try {
await mobileOptions.locator('#gateway-mode-tab').click();
const mobileTransformWorkbench = mobileOptions.locator('.transform-workbench');
await mobileTransformWorkbench.waitFor();
await mobileOptions.getByText('E2E login plaintext gateway', { exact: true }).waitFor();
await mobileOptions.locator('.transform-profile-list').getByText('E2E login plaintext gateway', { exact: true }).waitFor();
await mobileOptions.locator('.transform-data-flow').waitFor();
await mobileOptions.waitForTimeout(250);
const mobileTransformBounds = await mobileTransformWorkbench.evaluate((element) => ({
clientWidth: element.clientWidth,
@@ -2843,6 +3094,7 @@ try {
'browser.dom.write',
'browser.tab.activate', 'browser.page.invoke', 'browser.page.eval.expression', 'browser.human.takeover',
'browser.network.read', 'browser.network.capture', 'browser.network.sensitive.read',
'browser.isolation.read',
'browser.proxy.read', 'browser.proxy.write',
],
durationMinutes: 5,
@@ -2868,6 +3120,7 @@ try {
'browser.dom.write',
'browser.tab.activate', 'browser.page.invoke', 'browser.page.eval.expression', 'browser.human.takeover',
'browser.network.read', 'browser.network.capture', 'browser.network.sensitive.read',
'browser.isolation.read',
'browser.proxy.read', 'browser.proxy.write',
],
durationMinutes: 5,
@@ -2875,12 +3128,24 @@ try {
});
if (!response?.ok) throw new Error(response?.error || 'grant.create after reload');
}, { tabId: targetTab.id });
const staleAuthorizationBaseline = await callBridge(
bridgeSocket,
'verify-authorization-baseline-stale',
'browser.authorization.baseline.get',
{ id: authorizationBaseline.result.id },
);
if (staleAuthorizationBaseline.error?.code !== 'authorization_baseline_stale') {
throw new Error('Authorization baseline survived a document and grant replacement');
}
await options.evaluate(async () => await chrome.runtime.sendMessage({ action: 'panel.update', payload: { enabled: false } }));
await webPage.locator('.floating-panel__brand').waitFor({ state: 'hidden' });
await options.evaluate(async () => await chrome.runtime.sendMessage({ action: 'panel.update', payload: { enabled: true } }));
await webPage.locator('.floating-panel__brand').waitFor({ state: 'visible' });
await webPage.evaluate(() => dispatchEvent(new KeyboardEvent('keydown', { code: 'KeyY', altKey: true, shiftKey: true, bubbles: true })));
await webPage.evaluate(() => {
if (document.activeElement instanceof HTMLElement) document.activeElement.blur();
});
await webPage.keyboard.press('Alt+Shift+Y');
await webPage.locator('.floating-panel.is-expanded').waitFor();
await webPage.locator('.floating-panel__brand').click();
const currentOrigin = new URL(testUrl).origin;
@@ -3069,7 +3334,38 @@ try {
}
if (browserErrors.length > 0) throw new Error(`Browser page errors:\n${browserErrors.join('\n')}`);
console.log(JSON.stringify({ extensionId, testUrl, evalResult: evalResponse.data, timeoutError: timeoutResponse.error, bridgeEval: bridgeEval.result, cancelledBridgeError: cancelledBridgeEval.error, handoffEvent, auditEventCount: networkAudit.length, capturedNetworkUrl: capturedRequest.url, fuzzerPageId: 'e2e-fuzzer-page', protocolChecks, staleDocumentError: staleDocumentEval.error, staleOriginError: staleOriginEval.error?.message, serviceWorkerRestart: { beforeRestart, afterRestart }, unpairedIdentity, recorderPerformance: { idleCallsMs: idleRecorderDurationMs, ...recorderPerformance }, rightMetrics, panelMetrics, narrowPanel, artifacts }, null, 2));
console.log(JSON.stringify({
status: 'passed',
extensionId,
testOrigin: new URL(testUrl).origin,
checks: {
expressionEval: evalResponse.data?.value?.answer === 42,
timeout: Boolean(timeoutResponse.error),
bridgeEval: bridgeEval.result?.value?.answer === 42,
cancellation: cancelledBridgeEval.error?.code === 'cancelled',
handoff: handoffEvent.params?.state === 'completed',
authorizationBaseline: Boolean(authorizationBaseline.result?.id),
authorizationRecovery: staleAuthorizationBaseline.error?.code,
staleDocument: staleDocumentEval.error?.code,
staleOrigin: Boolean(staleOriginEval.error),
serviceWorkerRestart: beforeRestart.grantId === afterRestart.grantId,
stableInstallationIdentity: (
unpairedIdentity.beforeInstallationId === unpairedIdentity.afterInstallationId
),
},
auditEventCount: networkAudit.length,
capturedNetworkPath: new URL(capturedRequest.url).pathname,
recorderPerformance: {
idleCallsMs: idleRecorderDurationMs,
...recorderPerformance,
},
layout: {
floatingWidth: rightMetrics.width,
expandedHeight: panelMetrics.height,
narrowWidth: narrowPanel.width,
},
artifacts,
}, null, 2));
} finally {
await context?.close();
server.close();