feat: add web and Electron reverse-engineering workflows

This commit is contained in:
N0zoM1z0
2026-07-14 20:24:12 +09:00
committed by N0zoM1z0
parent c2e64a42c8
commit be76f8090b
106 changed files with 11893 additions and 404 deletions
+214 -68
View File
@@ -2,28 +2,34 @@
import { spawn } from "node:child_process";
import { access, mkdtemp, readFile, rm } from "node:fs/promises";
import { createServer } from "node:http";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { WebSocketServer } from "ws";
import { CdpBrowserProvider } from "../dist/browser/CdpBrowserProvider.js";
import {
inspectWebPageInputSchema,
listBrowserTargetsInputSchema,
} from "../dist/domain/browserObservation.js";
import { analyzeWebBundleInputSchema } from "../dist/domain/webBundleAnalysis.js";
import { observeWebSessionInputSchema } from "../dist/domain/browserSession.js";
import { compareWebCapturesInputSchema } from "../dist/domain/webCaptureDiff.js";
import {
captureWebScreenshotInputSchema,
compareWebScreenshotsInputSchema,
} from "../dist/domain/webScreenshot.js";
import { startBrowserVerifierSite } from "./fixtures/browser-verifier-site.mjs";
const SECRET_VALUES = [
"network-secret-value",
"console-secret-value",
"storage-secret-value",
"websocket-secret-value",
"ax-private-label-value",
];
const executable = await browserExecutable();
const profile = await mkdtemp(join(tmpdir(), "rea-real-browser-"));
const site = await startSite();
const site = await startBrowserVerifierSite();
let browser;
try {
browser = spawn(
@@ -77,19 +83,113 @@ try {
approved: true,
target_id: target,
observation_ms: 200,
include_accessibility_text: true,
include_script_sources: true,
include_console_text: true,
console_text_approved: true,
include_json_body_shapes: true,
json_body_schema_approved: true,
include_websocket_shapes: true,
websocket_shape_approved: true,
}),
);
if (!withSource.ok) throw withSource.error;
const source = withSource.value.scripts.items.find(
(script) =>
script.source.included &&
script.source.content.includes("reaSourceMarker"),
script.source.artifact.text.includes("reaSourceMarker"),
);
if (source === undefined)
throw new Error(
"Real Chrome did not return explicitly approved script source",
);
if (
!withSource.value.accessibility.nodes.some((node) =>
node.name?.includes("ax-private-label-value"),
)
)
throw new Error(
"Real Chrome did not return independently approved accessibility text",
);
assertSensitiveShapes(withSource.value);
const bundle = await provider.analyzeBundle(
analyzeWebBundleInputSchema.parse({
cdp_endpoint: endpoint,
allowed_origins: [site.origin],
approved: true,
target_id: target,
observation_ms: 200,
source_capture_approved: true,
fetch_source_maps: true,
source_map_fetch_approved: true,
}),
);
if (!bundle.ok) throw bundle.error;
assertBundleAnalysis(bundle.value);
const captureDiff = await provider.compareCaptures(
compareWebCapturesInputSchema.parse({
before: { inspection: observed.value },
after: { inspection: withSource.value },
}),
);
if (!captureDiff.ok) throw captureDiff.error;
if (
captureDiff.value.dimensions.dom_structure.status !== "unchanged" ||
captureDiff.value.dimensions.scripts.status !== "unchanged"
)
throw new Error("Real Chrome stable capture identities did not reconcile");
const screenshot = await provider.captureScreenshot(
captureWebScreenshotInputSchema.parse({
cdp_endpoint: endpoint,
allowed_origins: [site.origin],
approved: true,
screenshot_approved: true,
target_id: target,
}),
);
if (!screenshot.ok) throw screenshot.error;
if (
screenshot.value.viewport.width < 1 ||
screenshot.value.viewport.height < 1 ||
screenshot.value.artifact.bytes < 1
)
throw new Error("Real Chrome screenshot artifact was empty");
const screenshotDiff = await provider.compareScreenshots(
compareWebScreenshotsInputSchema.parse({
before: screenshot.value.artifact,
after: screenshot.value.artifact,
}),
);
if (!screenshotDiff.ok) throw screenshotDiff.error;
if (
screenshotDiff.value.status !== "identical" ||
screenshotDiff.value.changed_pixels !== 0
)
throw new Error("Real Chrome PNG artifact did not compare identically");
const sessionPromise = provider.observeSession(
observeWebSessionInputSchema.parse({
cdp_endpoint: endpoint,
allowed_origins: [site.origin],
approved: true,
target_id: target,
observation_ms: 1_500,
}),
);
await delay(250);
site.triggerSessionNavigation();
const session = await sessionPromise;
if (!session.ok) throw session.error;
if (
!session.value.timeline.some(
({ type }) => type === "same_document_navigation",
) ||
!session.value.target.final_url?.includes("/app/session-1")
)
throw new Error("Real Chrome same-origin SPA timeline was missing");
process.stdout.write(
`${JSON.stringify({
@@ -102,6 +202,10 @@ try {
networkRequests: observed.value.network.requests.length,
consoleEvents: observed.value.console.events.length,
websocketEvents: observed.value.network.websocket_events.length,
bundleScripts: bundle.value.capture.scripts_analyzed,
sourceMaps: bundle.value.observations.source_maps.processed,
sessionEvents: session.value.timeline.length,
screenshotBytes: screenshot.value.artifact.bytes,
verified: true,
})}\n`,
);
@@ -178,6 +282,13 @@ function assertObservation(result, origin) {
);
if (result.accessibility.nodes.length < 1)
throw new Error("Real Chrome accessibility observation was empty");
if (
result.accessibility.text_capture.status !== "not_approved" ||
result.accessibility.nodes.some(
(node) => node.name !== null || node.description !== null,
)
)
throw new Error("Accessibility text was retained without approval");
if (!result.scripts.items.some((script) => script.url.includes("/app.js")))
throw new Error("Real Chrome script metadata was missing");
if (!result.resources.some((resource) => resource.url.includes("/app.js")))
@@ -186,86 +297,121 @@ function assertObservation(result, origin) {
throw new Error(
"Real Chrome attach-window network observation was missing",
);
const initiatedRequest = result.network.requests.find((request) =>
request.url.includes("/api"),
);
if (
!initiatedRequest?.initiator.url?.includes("/app.js") ||
initiatedRequest.initiator.line === null ||
initiatedRequest.initiator.column === null
)
throw new Error("Real Chrome script initiator stack was not normalized");
if (result.console.events.length < 1)
throw new Error(
"Real Chrome attach-window console observation was missing",
);
if (result.network.websocket_events.length < 1)
throw new Error("Real Chrome WebSocket metadata was missing");
if (
result.console.events.some(
(event) => event.text_capture.status !== "not_approved",
) ||
result.network.requests.some(
(request) => request.body_shapes.status !== "not_approved",
) ||
result.network.websocket_events.some(
(event) => event.payload_shape !== null,
)
)
throw new Error(
"Sensitive text or payload shapes were retained without approval",
);
if (!result.storage.local_storage_keys.includes("rea-storage-key"))
throw new Error("Real Chrome local-storage key inventory was missing");
if (!result.storage.indexed_db_names.includes("rea-browser-db"))
throw new Error("Real Chrome IndexedDB name inventory was missing");
if (!result.storage.cache_names.includes("rea-browser-cache"))
throw new Error("Real Chrome cache name inventory was missing");
if (
!result.metadata.responses.some(({ csp }) =>
csp.directives.some(({ name }) => name === "default-src"),
) ||
!result.metadata.agent_hints.some(
({ declaration }) => declaration === "service-desc",
)
)
throw new Error("Real Chrome safe response metadata was missing");
}
async function startSite() {
let port = 0;
const server = createServer((request, response) => {
if (request.url?.startsWith("/app.js") === true) {
response.setHeader("content-type", "text/javascript");
response.end(script(port));
return;
}
if (request.url?.startsWith("/api") === true) {
response.setHeader("content-type", "application/json");
response.end('{"ok":true,"secret":"response-secret-value"}');
return;
}
response.setHeader("content-type", "text/html");
response.end(
'<!doctype html><html><head><title>REA browser verifier</title></head><body><main><h1>Browser evidence</h1><button aria-label="Verify browser">Verify</button></main><script type="module" src="/app.js?token=script-query-secret"></script></body></html>',
function assertBundleAnalysis(result) {
if (
result.capture.scripts_analyzed < 1 ||
!result.observations.routes.some(({ value }) =>
value.includes("/verified-route"),
) ||
!result.observations.endpoints.some(({ value }) => value.includes("/api"))
)
throw new Error("Real Chrome static bundle findings were missing");
if (
result.observations.source_maps.status !== "included" ||
!result.observations.source_maps.items.some(
({ status, original_sources, original_module_edges, mappings }) =>
status === "included" &&
original_sources.some(({ source }) =>
source.includes("/src/main.ts"),
) &&
original_module_edges.some(
({ specifier }) => specifier === "./dependency.ts",
) &&
mappings.length > 0,
)
)
throw new Error(
"Real Chrome approved source-map reconstruction was missing",
);
});
const webSockets = new WebSocketServer({ noServer: true });
server.on("upgrade", (request, socket, head) => {
webSockets.handleUpgrade(request, socket, head, (webSocket) => {
webSocket.on("message", () =>
webSocket.send("websocket-response-secret"),
);
});
});
await new Promise((resolve, reject) => {
server.once("error", reject);
server.listen(0, "127.0.0.1", resolve);
});
const address = server.address();
if (address === null || typeof address === "string")
throw new Error("Local browser verifier did not bind a TCP port");
port = address.port;
return {
origin: `http://127.0.0.1:${String(port)}`,
async close() {
for (const client of webSockets.clients) client.terminate();
await new Promise((resolve) => webSockets.close(resolve));
await new Promise((resolve, reject) =>
server.close((error) =>
error === undefined ? resolve() : reject(error),
),
);
},
};
}
function script(port) {
return `
export const reaSourceMarker = "approved-source-marker";
localStorage.setItem("rea-storage-key", "storage-secret-value");
indexedDB.open("rea-browser-db", 1);
caches.open("rea-browser-cache");
const observe = () => {
console.log("rea-browser-observation", "console-secret-value");
fetch("/api?token=network-secret-value", {
headers: { Authorization: "Bearer request-secret-value" }
});
const socket = new WebSocket("ws://127.0.0.1:${String(port)}/live?token=websocket-url-secret");
socket.addEventListener("open", () => socket.send("websocket-secret-value"));
socket.addEventListener("message", () => socket.close());
};
observe();
setInterval(observe, 150);
`;
function assertSensitiveShapes(result) {
const request = result.network.requests.find((item) =>
item.url.includes("/api"),
);
const requestPaths = request?.body_shapes.request?.properties.map(
({ path }) => path,
);
const responsePaths = request?.body_shapes.response?.properties.map(
({ path }) => path,
);
if (!requestPaths?.includes("/token") || !responsePaths?.includes("/secret"))
throw new Error("Real Chrome JSON request/response shapes were missing");
const consoleText = result.console.events.flatMap(
(event) => event.text_capture.values,
);
if (
!consoleText.some(({ text }) => text.includes("[REDACTED]")) ||
consoleText.some(({ text }) => text.includes("console-secret-value"))
)
throw new Error(
"Real Chrome approved console text was not safely redacted",
);
if (
!result.network.websocket_events.some(
(event) =>
event.payload_shape?.format === "json" &&
event.payload_shape.json_shape?.properties.some(
({ path }) => path === "/token",
),
)
)
throw new Error("Real Chrome WebSocket JSON shape was missing");
const serialized = JSON.stringify(result);
for (const secret of [
"request-body-secret-value",
"response-secret-value",
"websocket-secret-value",
"console-secret-value",
])
if (serialized.includes(secret))
throw new Error(`Approved shape capture retained raw value: ${secret}`);
}
function delay(milliseconds) {