fix(capture): automate safe bidirectional gateways for users and agents

This commit is contained in:
go0p
2026-09-17 17:24:54 +08:00
parent 45f53604e8
commit 601f8acbcf
48 changed files with 2111 additions and 516 deletions
+113
View File
@@ -0,0 +1,113 @@
import assert from 'node:assert/strict'
import { spawn } from 'node:child_process'
import { resolve } from 'node:path'
import { createDecipheriv } from 'node:crypto'
import { extensionRequest, launchBrowserAgentContractHarness } from './browser-agent-contract-harness.mjs'
function body(packet) { return JSON.parse(packet.raw.slice(packet.raw.indexOf('\r\n\r\n') + 4)) }
function decrypt(envelope) {
const decipher = createDecipheriv('aes-128-cbc', Buffer.from(envelope.key, 'hex'), Buffer.from(envelope.iv, 'hex'))
return JSON.parse(Buffer.concat([decipher.update(Buffer.from(envelope.message, 'base64')), decipher.final()]).toString())
}
const targetURL = process.env.LOGIN_TARGET || 'http://localhost:8080/crypto/sqli/aes-ecb/encrypt/login'
const engine = spawn('go', ['test', './common/yakgrpc', '-run', '^TestBrowserAgentLiveGateway$', '-count=1', '-v'], {
cwd: process.env.YAKLANG_ROOT || resolve(import.meta.dirname, '../../../go/yaklang'),
env: { ...process.env, YAK_BROWSER_AGENT_E2E: '1' }, stdio: ['ignore', 'pipe', 'pipe'],
})
let output = ''
let harness, connection
const exited = new Promise(resolve => engine.once('exit', code => resolve(code)))
const ready = new Promise((resolve, reject) => {
const timer = setTimeout(() => reject(new Error(`Go Agent bridge startup timed out: ${output.slice(-4000)}`)), 120_000)
engine.stdout.on('data', chunk => {
output += chunk
const match = output.match(/YAK_AGENT_E2E=(\{[^\n]+\})/)
if (match) { clearTimeout(timer); resolve(JSON.parse(match[1])) }
})
engine.stderr.on('data', chunk => { output += chunk })
engine.once('error', error => { clearTimeout(timer); reject(error) })
engine.once('exit', code => { clearTimeout(timer); reject(new Error(`Go bridge exited ${code}: ${output}`)) })
})
try {
connection = await ready
const tool = async (name, params = {}) => {
const response = await fetch(connection.endpoint, { method: 'POST', headers: { 'X-Test-Token': connection.token }, body: JSON.stringify({ tool: name, params }) })
if (!response.ok) throw new Error(`${name}: ${await response.text()}`)
return response.json()
}
harness = await launchBrowserAgentContractHarness({ profilePrefix: 'yakit-agent-gateway-', targetURL })
const { controlPage, targetPage, tabId } = harness
let completed = 0, blocked = 0
targetPage.on('response', response => { if (response.url() === targetURL && response.request().method() === 'POST') completed++ })
targetPage.on('requestfailed', request => { if (request.url() === targetURL && request.failure()?.errorText.includes('BLOCKED_BY_CLIENT')) blocked++ })
const state = await controlPage.evaluate(async () => (await chrome.runtime.sendMessage({ action: 'state.get' })).data)
await extensionRequest(controlPage, 'bridge.config.save', {
transport: 'websocket', nativeHost: 'com.yaklang.browser_agent', endpoint: connection.bridge,
autoConnect: false, installationId: state.bridge.installationId,
})
await controlPage.evaluate(async () => {
const result = await chrome.runtime.sendMessage({ action: 'bridge.pair' })
if (!result.ok) throw new Error(JSON.stringify(result.error))
})
for (let attempt = 0; ; attempt++) {
const status = await controlPage.evaluate(async () => (await chrome.runtime.sendMessage({ action: 'bridge.status' })).data)
if (status.state === 'connected') break
if (attempt === 100) throw new Error(`Bridge did not connect: ${JSON.stringify(status)}`)
await controlPage.waitForTimeout(100)
}
// Close the extension UI. All workflow calls below go through Go Agent tools.
// A previously released human debugging session must not lock out the Agent.
await extensionRequest(controlPage, 'deep.capture.start', { tabId, frameId: 0, matcher: { kind: 'request', urlPattern: '/not-triggered' } })
await extensionRequest(controlPage, 'deep.capture.detach', { tabId, frameId: 0 })
await controlPage.close()
const call = (method, params = {}) => tool('browser.capability.call', { method, params: { tabId, frameId: 0, ...params } })
const catalog = await tool('browser.capability.catalog', { domain: 'debugger' })
assert(catalog.capabilities.some(item => item.method === 'browser.deep_capture.start'))
const context = await call('browser.context', { includeDom: true })
const nodes = context.document.interactive
const username = nodes.find(node => node.name === 'username' || node.id === 'username')
const password = nodes.find(node => node.name === 'password' || node.id === 'password')
const submit = nodes.find(node => node.tag === 'button' && node.type === 'submit')
assert(username && password && submit, JSON.stringify(nodes))
for (const [node, value] of [[username, 'agent-original'], [password, 'agent-original-wrong']]) {
await call('browser.node.action', { captureId: context.captureId, nodeId: node.nodeId, action: 'setValue', value })
}
const inspection = await tool('browser.crypto.inspect', { tabId, frameId: 0, captureId: context.captureId, nodeId: submit.nodeId })
assert.equal(inspection.gatewayPreparation.state, 'capture-required')
const plaintext = { username: 'agent-new-user', password: 'agent-new-wrong' }
const url = new URL(targetURL)
const request = `POST ${url.pathname} HTTP/1.1\r\nHost: ${url.host}\r\nContent-Type: application/json\r\n\r\n${JSON.stringify(plaintext)}`
const prepareInput = { ...inspection.target, candidate_id: inspection.gatewayPreparation.candidateId, request, is_https: false }
await assert.rejects(tool('browser.transform.prepare', { ...prepareInput, captureId: 'stale-context', nodeId: submit.nodeId }), /快照已经失效/)
assert.equal((await call('browser.deep_capture.status')).state, 'detached', 'failed preparation must release its debugger')
assert.equal(completed, 1)
const prepared = await tool('browser.transform.prepare', prepareInput)
assert(prepared.valid, JSON.stringify(prepared))
assert.deepEqual(prepared.validationDraft.directions, { request: true, response: true })
assert.equal(completed, 1, 'automatic preparation must not submit another real browser login')
assert.equal(blocked, 1)
const preparedAgain = await tool('browser.transform.prepare', prepareInput)
assert(preparedAgain.valid)
assert.equal(blocked, 1, 'repeated preparation must reuse existing captured functions')
const tested = await tool('browser.http.test', { validation_id: preparedAgain.validationDraft.id, request, is_https: false })
assert(tested.responseTransformEnabled, JSON.stringify(tested))
assert(tested.requestTransformed, JSON.stringify(tested))
assert(tested.responseTransformed)
assert.deepEqual(decrypt(body(tested.wireRequest)), plaintext)
assert.deepEqual(body(tested.plaintextResponse), decrypt(body(tested.wireResponse)))
const draft = await call('browser.profile.validation.latest')
const saved = await tool('browser.capability.call', { method: 'browser.transform.profile.save', params: draft.profile })
assert(saved.request.enabled && saved.response.enabled)
await tool('browser.capability.call', { method: 'browser.transform.profile.delete', params: { id: saved.id } })
assert.equal(completed, 1)
assert.equal(await targetPage.locator('#username').inputValue(), 'agent-original')
assert.equal(await targetPage.locator('#password').inputValue(), 'agent-original-wrong')
console.log('Agent gateway passed: signed Go bridge, advanced capability access, no plugin UI, failed-capture cleanup, automatic business capture, bidirectional validation, independently checked request/response crypto, default response decryption and Profile save/delete.')
} finally {
await harness?.close()
if (connection) await fetch(`${connection.endpoint}/finish`, { method: 'POST', headers: { 'X-Test-Token': connection.token } }).catch(() => undefined)
else engine.kill('SIGTERM')
const code = await exited
if (code !== 0 && connection) throw new Error(`Go integration exited ${code}: ${output.slice(-4000)}`)
}
+142
View File
@@ -0,0 +1,142 @@
import assert from 'node:assert/strict'
import { createServer } from 'node:http'
import { readFile } from 'node:fs/promises'
import { extensionRequest, launchBrowserAgentContractHarness, waitFor } from './browser-agent-contract-harness.mjs'
const registryKey = (await readFile(new URL('../src/features/page-callable/constants.ts', import.meta.url), 'utf8')).match(/= '([^']+)'/)[1]
const received = []
const server = createServer(async (request, response) => {
let body = ''
for await (const chunk of request) body += chunk
received.push({ url: request.url, method: request.method, body })
if (request.url === '/key' || request.url === '/bad-key') {
response.setHeader('Content-Type', 'application/json')
response.end(JSON.stringify(request.url === '/key' ? { key: 'fresh-server-key' } : {}))
} else if (request.method === 'POST') {
response.setHeader('Content-Type', 'application/json')
response.end('{}')
} else {
response.setHeader('Content-Type', 'text/html')
response.end('<form id="form"><input name="password" value="old"><button>Submit</button></form><script>globalThis.cachedFetch = fetch.bind(window); globalThis.cachedSubmit = HTMLFormElement.prototype.submit;</script>')
}
})
await new Promise(resolve => server.listen(0, '127.0.0.1', resolve))
const targetURL = `http://127.0.0.1:${server.address().port}/`
let harness
try {
harness = await launchBrowserAgentContractHarness({ profilePrefix: 'yakit-capture-regressions-', targetURL })
const { controlPage, targetPage, tabId } = harness
const target = { tabId, frameId: 0 }
const request = (action, payload = {}) => extensionRequest(controlPage, action, { ...target, ...payload })
await request('recording.start', { captureValues: true, maxEntries: 200, maxValueBytes: 8192 })
await targetPage.evaluate(async () => {
await fetch(new Request(location.origin + '/request-object', { method: 'POST', body: 'encrypted-request-body' }))
})
const recorded = await waitFor(controlPage, 'recording.get', target, snapshot => snapshot.events.some(event =>
event.url?.endsWith('/request-object') && event.inputs.some(value => value.path === '$body' && value.preview === 'encrypted-request-body')))
assert(recorded.events.some(event => event.url?.endsWith('/request-object')))
async function register(mode, transaction = false) {
return targetPage.evaluate(({ key, mode, transaction }) => {
const id = crypto.randomUUID()
const endpoint = location.origin + '/' + mode
const savedState = { password: 'old' }
const prerequisites = mode.startsWith('prerequisite') ? [{
boundary: 'fetch', method: 'GET', url: location.origin + (mode === 'prerequisite-invalid' ? '/bad-key' : '/key'),
requestBodyFormat: 'none', maxRequestBodyBytes: 0,
response: { statusCode: 200, url: location.origin + (mode === 'prerequisite-invalid' ? '/bad-key' : '/key'), bodyFormat: 'json', maxBodyBytes: 4096, requiredPaths: ['body.key'] },
}] : []
if (mode === 'controlled') document.querySelector('input').addEventListener('input', event => { savedState.password = event.target.value })
const metadata = {
id, name: mode, kind: transaction ? 'request-transaction' : 'business-closure', operation: mode,
origin: location.origin, lifecycle: 'document', execution: { resultMode: 'auto', timeoutMs: 1500 },
inputSlots: [{ id: 'body', name: 'body', index: 0, role: 'data', dataType: 'object', required: true, retained: false }],
output: { dataType: transaction ? 'object' : 'string', encoding: transaction ? 'json' : 'utf8', shape: transaction ? 'envelope' : 'value', paths: transaction ? ['body.password'] : [] },
provenance: {}, createdAt: Date.now(),
...(transaction ? { transaction: { version: 2, prerequisites, inputMode: 'auto', request: {
boundary: mode === 'multipart-xhr' ? 'xhr' : mode === 'multipart-beacon' ? 'beacon' : 'fetch',
method: 'POST', url: endpoint, bodyFormat: mode.startsWith('multipart') ? 'form' : 'json', expectedDestinations: ['body.password'],
} } } : {}),
}
const registry = globalThis[key] ||= new Map()
registry.set(id, { metadata, invoke(args) {
if (mode === 'native-form') {
const form = document.querySelector('form'); form.method = 'POST'; form.action = endpoint
Reflect.apply(globalThis.cachedSubmit, form, []); return 'unexpected'
}
if (mode === 'cached') return globalThis.cachedFetch(endpoint, { method: 'POST', body: 'leak' }).then(() => 'bad')
if (mode === 'timer') { setTimeout(() => fetch(endpoint, { method: 'POST', body: 'leak' }), 100); return 'ok' }
const value = mode === 'stale' || mode === 'controlled' ? savedState : args[0]
const send = () => {
if (mode.startsWith('multipart')) {
const form = new FormData(); form.set('password', value.password)
if (mode === 'multipart-xhr') { const xhr = new XMLHttpRequest(); xhr.open('POST', endpoint); return xhr.send(form) }
if (mode === 'multipart-beacon') return navigator.sendBeacon(endpoint, form)
return fetch(endpoint, { method: 'POST', body: form })
}
return fetch(endpoint, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(value) })
}
if (mode.startsWith('concurrent')) return new Promise(resolve => setTimeout(resolve, 40)).then(send)
if (prerequisites.length) return fetch(mode === 'prerequisite-unplanned' ? '/unplanned' : prerequisites[0].url).then(response => response.json()).then(send)
return send()
} })
return id
}, { key: registryKey, mode, transaction })
}
const execute = (id, password = 'new') => request('callable.execute', { callableId: id, args: [{ password }] })
await assert.rejects(execute(await register('cached')), /fetch|网络|阻止|Failed/i)
assert.equal((await execute(await register('timer'))).value, 'ok')
await targetPage.waitForTimeout(160)
assert(!received.some(item => ['/cached', '/timer'].includes(item.url)))
await assert.rejects(execute(await register('stale', true)), /新明文|旧状态/)
for (const mode of ['controlled', 'multipart', 'multipart-xhr', 'multipart-beacon']) {
assert.deepEqual((await execute(await register(mode, true))).value, { password: 'new' })
assert.equal(await targetPage.locator('input').inputValue(), 'old')
}
const concurrent = await Promise.all([register('concurrent-a', true), register('concurrent-b', true)])
const results = await Promise.all(concurrent.map((id, index) => execute(id, `new-${index}`)))
assert.deepEqual(results.map(result => result.value), [{ password: 'new-0' }, { password: 'new-1' }])
assert(!received.some(item => ['/stale', '/controlled', '/multipart', '/multipart-xhr', '/multipart-beacon', '/concurrent-a', '/concurrent-b'].includes(item.url)))
assert.deepEqual((await execute(await register('prerequisite', true))).value, { password: 'new' })
assert.equal(received.filter(item => item.url === '/key').length, 1)
await assert.rejects(execute(await register('prerequisite-invalid', true)), /缺少目标字段/)
await assert.rejects(execute(await register('prerequisite-unplanned', true)), /未授权请求/)
assert(!received.some(item => item.url === '/unplanned' || item.method === 'POST' && item.url.startsWith('/prerequisite')))
assert.equal((await controlPage.evaluate(() => chrome.declarativeNetRequest.getSessionRules())).length, 0)
const nonWritableId = await register('timer')
await targetPage.evaluate(() => {
globalThis.beforeIsolation = { fetch, setTimeout, sendBeacon: navigator.sendBeacon }
Object.defineProperty(navigator, 'sendBeacon', { value: navigator.sendBeacon, writable: false, configurable: true })
})
await assert.rejects(execute(nonWritableId), /不能隔离页面边界/)
assert(await targetPage.evaluate(() => {
const restored = fetch === globalThis.beforeIsolation.fetch && setTimeout === globalThis.beforeIsolation.setTimeout
Object.defineProperty(navigator, 'sendBeacon', { value: globalThis.beforeIsolation.sendBeacon, writable: true, configurable: true })
return restored
}))
assert.equal((await controlPage.evaluate(() => chrome.declarativeNetRequest.getSessionRules())).length, 0)
// A strict arrow listener in a one-line script must resolve by its exact function location.
await targetPage.addScriptTag({ content: `document.querySelector('form').addEventListener('submit', e => { 'use strict'; e.preventDefault(); fetch('/strict', { method: 'POST', body: 'cipher' }) }); document.querySelector('form').addEventListener('submit', e => { 'use strict'; e.preventDefault() });` })
await request('deep.capture.start', { matcher: { kind: 'request', urlPattern: '/strict' } })
const submit = targetPage.locator('form button').click({ noWaitAfter: true, timeout: 20_000 })
const paused = await waitFor(controlPage, 'deep.capture.status', target, value => value.state === 'paused' && !value.pause.collecting)
const resolved = paused.pause.frames.filter(frame => frame.functionInspection?.resolution === 'event-listener')
assert.equal(resolved.length, 1, JSON.stringify(paused.pause))
assert(resolved[0].functionInspection.resolved)
await request('deep.capture.resume')
await submit
const nativeFormId = await register('native-form')
const blockedForm = targetPage.waitForEvent('requestfailed', { predicate: request => request.url().endsWith('/native-form'), timeout: 10_000 })
void blockedForm.catch(() => undefined)
let nativeFormError
await assert.rejects(execute(nativeFormId), error => { nativeFormError = error.message; return true })
const failedForm = await blockedForm.catch(error => { throw new Error(`${error.message}; execution=${nativeFormError}; url=${targetPage.url()}; received=${JSON.stringify(received)}`) })
assert(failedForm.failure().errorText.includes('BLOCKED_BY_CLIENT'))
assert(!received.some(item => item.url === '/native-form'))
console.log('Capture regressions passed: Request body, native network guard, timer cleanup, fresh input, controlled forms, multipart, concurrent callables, strict same-line listeners.')
} finally {
await harness?.close()
await new Promise(resolve => server.close(resolve))
}
+103
View File
@@ -0,0 +1,103 @@
import assert from 'node:assert/strict'
import { createDecipheriv } from 'node:crypto'
import { extensionRequest, launchBrowserAgentContractHarness, transformedFetchOptions, waitFor } from './browser-agent-contract-harness.mjs'
const targetURL = process.env.LOGIN_TARGET || 'http://localhost:8080/crypto/sqli/aes-ecb/encrypt/login'
function decrypt(envelope) {
const decipher = createDecipheriv('aes-128-cbc', Buffer.from(envelope.key, 'hex'), Buffer.from(envelope.iv, 'hex'))
return JSON.parse(Buffer.concat([decipher.update(Buffer.from(envelope.message, 'base64')), decipher.final()]).toString())
}
let harness
try {
harness = await launchBrowserAgentContractHarness({ profilePrefix: 'yakit-login-gateway-', targetURL })
const { targetPage, controlPage, tabId, extensionId } = harness
const target = { tabId, frameId: 0 }
let completedPosts = 0
let blockedPosts = 0
targetPage.on('response', response => { if (response.url() === targetURL && response.request().method() === 'POST') completedPosts++ })
targetPage.on('requestfailed', request => {
if (request.url() === targetURL && request.method() === 'POST' && request.failure()?.errorText.includes('BLOCKED_BY_CLIENT')) blockedPosts++
})
await extensionRequest(controlPage, 'recording.start', { ...target, captureValues: true, maxEntries: 300, maxValueBytes: 8192 })
await targetPage.locator('#username').fill('audit-original')
await targetPage.locator('#password').fill('original-wrong-password')
await targetPage.locator('button[type=submit]').click()
const recorded = await waitFor(controlPage, 'recording.get', target, snapshot =>
snapshot.profileCandidates.some(candidate => candidate.direction === 'response' && candidate.status === 'ready'))
await extensionRequest(controlPage, 'recording.stop', target)
assert.equal(completedPosts, 1)
const responseCandidate = recorded.profileCandidates.find(candidate => candidate.direction === 'response' && candidate.status === 'ready')
const requestCandidate = recorded.profileCandidates.find(candidate => candidate.direction === 'request' && candidate.transactionId === responseCandidate.transactionId)
assert.equal(requestCandidate.status, 'capture-required', JSON.stringify(recorded.profileCandidates))
await controlPage.goto(`chrome-extension://${extensionId}/options.html?tabId=${tabId}#gateway`)
await controlPage.evaluate(() => {
globalThis.captureActions = []
const send = chrome.runtime.sendMessage.bind(chrome.runtime)
chrome.runtime.sendMessage = (...args) => { globalThis.captureActions.push(args[0]); return send(...args) }
})
const traceIndex = recorded.traces.findIndex(trace => trace.id === responseCandidate.traceId)
await controlPage.locator('.recording-traces button').nth(traceIndex).click()
await controlPage.locator(`[data-event-id="${responseCandidate.source.eventId}"]`).click()
await controlPage.getByRole('button', { name: '继续捕获请求方向', exact: true }).click()
await waitFor(controlPage, 'deep.capture.status', target, status => status.state === 'armed')
let submitError
const submit = targetPage.locator('button[type=submit]').click({ noWaitAfter: true, timeout: 45_000 }).catch(error => { submitError = error })
let profiles
try {
profiles = await waitFor(controlPage, 'transform.profile.list', {}, values => values.some(profile => profile.request.enabled && profile.response.enabled), 30_000)
} catch (error) {
const status = await extensionRequest(controlPage, 'deep.capture.status', target)
if (status.pause) status.pause.frames = status.pause.frames.map(({ scopes, ...frame }) => frame)
const actions = await controlPage.evaluate(() => globalThis.captureActions.filter(item => /create|start|save/.test(item.action)))
throw new Error(`${error.message}\n${JSON.stringify(status)}\n${JSON.stringify(actions)}\n${await controlPage.locator('body').innerText()}`)
}
await submit
if (submitError) throw submitError
assert.equal(completedPosts, 1, 'the capture submission must not reach the server')
assert.equal(blockedPosts, 1, 'the browser must confirm cancellation of the capture submission')
const profile = profiles.find(value => value.request.enabled && value.response.enabled)
assert.equal(profiles.length, 1, 'both directions must share one gateway')
for (const plaintext of [
{ username: 'audit-new-user', password: 'new-wrong-password' },
{ username: 'audit-second-user', password: 'second-wrong-password' },
]) {
const packet = { method: 'POST', url: targetURL, headers: [{ name: 'Content-Type', value: 'application/json' }], bodyBase64: Buffer.from(JSON.stringify(plaintext)).toString('base64') }
const encrypted = await extensionRequest(controlPage, 'transform.execute', { profileId: profile.id, direction: 'request', packet })
const envelope = JSON.parse(Buffer.from(encrypted.bodyBase64, 'base64').toString())
assert.deepEqual(decrypt(envelope), plaintext, 'the complete new input must be encrypted')
const response = await fetch(targetURL, transformedFetchOptions(encrypted, packet.headers))
const wireResponse = await response.text()
const decrypted = await extensionRequest(controlPage, 'transform.execute', {
profileId: profile.id, direction: 'response', packet: { ...packet, bodyBase64: Buffer.from(wireResponse).toString('base64') },
})
assert.deepEqual(JSON.parse(Buffer.from(decrypted.bodyBase64, 'base64').toString()), decrypt(JSON.parse(wireResponse)))
}
assert.equal(completedPosts, 1, 'local gateway replay must not send browser requests')
assert.equal(await targetPage.locator('#username').inputValue(), 'audit-original')
assert.equal(await targetPage.locator('#password').inputValue(), 'original-wrong-password')
// Also pause inside the native Fetch boundary, where changing window.fetch is too late.
await extensionRequest(controlPage, 'deep.capture.start', {
...target, matcher: { kind: 'request', urlPattern: targetURL, frameHints: requestCandidate.capturePlan.frameHints },
})
const boundaryBlocked = targetPage.waitForEvent('requestfailed', {
predicate: request => request.url() === targetURL && request.failure()?.errorText.includes('BLOCKED_BY_CLIENT'),
timeout: 30_000,
})
void boundaryBlocked.catch(() => undefined)
const boundarySubmit = targetPage.locator('button[type=submit]').click({ noWaitAfter: true, timeout: 30_000 }).catch(error => { submitError = error })
const paused = await waitFor(controlPage, 'deep.capture.status', target, status => status.state === 'paused' && !status.pause.collecting)
assert.equal(paused.pause.automaticCapture.state, 'ready')
const boundaryCallable = await extensionRequest(controlPage, 'callable.create', {
...target, source: 'deep-capture', strategy: 'request-transaction',
callFrameId: paused.pause.automaticCapture.frameId, candidateId: requestCandidate.id,
})
await boundarySubmit
await boundaryBlocked
if (submitError) throw submitError
assert.equal(completedPosts, 1)
assert.equal(blockedPosts, 2)
await extensionRequest(controlPage, 'callable.delete', { ...target, callableId: boundaryCallable.id })
console.log('Login gateway passed: one recording, automatic request capture, browser-confirmed cancellation, one bidirectional profile, two distinct plaintexts and real server response decryption.')
} finally { await harness?.close() }