Files
ycode/scripts/rea/recover-agents-browser-icons.mjs

74 lines
3.7 KiB
JavaScript

// Recover only the three browser-extension images retained by REA Evidence.
// This script reads the archived Evidence JSON and never accesses the WorkBuddy install.
import { createHash } from 'node:crypto'
import fs from 'node:fs/promises'
import path from 'node:path'
const evidenceId = 'ev_9542a063514b1a980665e504a89126c7a17f00b42f0c6dff9cead085a929f5c7'
const evidencePath = path.resolve(`.tmp/rea-css-evidence/${evidenceId}.json`)
const outputDirectory = path.resolve('src/renderer/src/components/agents-assets')
const expected = [
{
selector: '#settings-browser-extension-chrome img',
mimeType: 'image/svg+xml',
sha256: 'bde4af5a5d1150d3ec3108c29a5d746a00892196f3eaf1db4c4dc99aac7ba266',
output: 'workbuddy-browser-chrome.svg'
},
{
selector: '#settings-browser-extension-edge img',
mimeType: 'image/svg+xml',
sha256: '968cf9d827bfeaf47e4e53da55ec144bbc9565e5cb7a65cdb8dd0284fb3a5d11',
output: 'workbuddy-browser-edge.svg'
},
{
selector: '#settings-browser-extension-qq-browser img',
mimeType: 'image/png',
sha256: '186c82828c04b5bfe300c201ecd75cefb7984d97ea01bd491e0cfbdeb5df15a1',
output: 'workbuddy-browser-qq.png'
}
]
const digest = (bytes) => createHash('sha256').update(bytes).digest('hex')
const evidence = JSON.parse(await fs.readFile(evidencePath, 'utf8'))
if (evidence.evidence_id !== evidenceId) throw new Error(`REA identity mismatch: ${evidence.evidence_id}`)
if (evidence.completeness?.truncated_sections?.length) throw new Error('REA Evidence is truncated')
if (!/renderer[\\/]index\.html$/i.test(evidence.normalized_result?.target?.file_path ?? '')) {
throw new Error('REA Evidence target is not the authorized WorkBuddy renderer')
}
const media = evidence.normalized_result?.visuals?.media_assets
if (!media?.items || media.items.length !== expected.length) throw new Error('Unexpected REA media item count')
const decoded = []
for (const item of expected) {
const matches = media.items.filter((candidate) => candidate.selector === item.selector)
if (matches.length !== 1) throw new Error(`Expected one REA item for ${item.selector}`)
const asset = matches[0]
if (asset.status !== 'exported' || asset.visibility?.status !== 'observed') {
throw new Error(`REA did not export a visible image for ${item.selector}: ${asset.reason}`)
}
if (asset.artifact?.mime_type !== item.mimeType || asset.artifact.sha256 !== item.sha256) {
throw new Error(`REA artifact identity changed for ${item.selector}`)
}
const bytes = Buffer.from(asset.artifact.data_base64, 'base64')
if (bytes.length !== asset.artifact.bytes || digest(bytes) !== item.sha256) {
throw new Error(`REA artifact bytes changed for ${item.selector}`)
}
if (item.mimeType === 'image/svg+xml') {
const source = bytes.toString('utf8')
if (!/^<svg\s/.test(source) || !/\bviewBox=['"]/i.test(source)) throw new Error(`Invalid SVG source for ${item.output}`)
if (/<script\b|<foreignObject\b|\b(?:href|xlink:href)=['"](?:https?:|file:|data:)/i.test(source)) {
throw new Error(`Unsafe external or active SVG content for ${item.output}`)
}
} else if (item.mimeType === 'image/png') {
if (bytes.subarray(0, 8).toString('hex') !== '89504e470d0a1a0a') throw new Error(`Invalid PNG source for ${item.output}`)
if (bytes.readUInt32BE(16) !== 128 || bytes.readUInt32BE(20) !== 128) throw new Error('QQ browser icon is not 128x128')
}
decoded.push({ bytes, output: item.output, sha256: item.sha256 })
}
await fs.mkdir(outputDirectory, { recursive: true })
for (const asset of decoded) await fs.writeFile(path.join(outputDirectory, asset.output), asset.bytes)
console.log(JSON.stringify({ evidenceId, outputDirectory, assets: decoded.map(({ output, sha256, bytes }) => ({
output, sha256, bytes: bytes.length
})) }, null, 2))