Files
reverse-engineering-anything/src/browser/WebBundleAnalysisWorkerServer.ts
T
morluto cb4e5cfbff feat(analysis): make resource budgets recoverable across CLI and MCP (#9706)
* refactor(analysis): centralize resource budgets and recovery facts

* feat(analysis): expose ASAR and Ghidra runtime admission controls

* feat(artifacts): expose reader admission and preserve limited recognition

* feat(javascript): expose recoverable semantic precision budgets

* feat(web): add recoverable source-map budgets and compact retained views

* feat(web): make historical capture decoding budgets recoverable

* fix(desktop): enforce selected view policies across capture and export

* feat(runtime): make Frida and LLDB retention budgets selectable

* feat(native): expose diagnostic budgets and preserve exhaustion causes

* feat(managed): expose recoverable snapshot and representation budgets

* feat(metadata): make Go and Rust inspection budgets recoverable

* feat(wasm): expose representation and producer admissions

* feat(evm): expose per-call worker resource budgets

* feat(android): make resource and string-pool omissions recoverable

* feat(producers): expose command and recorded-capture budgets

* docs(analysis): record recovery contracts and integrated validation

* fix(docs): link calibration oracle from published verification pages

* fix(docs): restore desktop task anchors and receipt links

* test(analysis): align fixtures with selectable budget contracts

* fix(analysis): remove source-location projector dependency cycle

* test(analysis): refresh budget recovery and verifier regressions

* test(analysis): preserve format and publication recovery expectations

* test(windows): align conformance with owned runtime admission

* fix(desktop): preserve selected alias kinds in exported state

* test(analysis): align provider fixtures with recoverable budgets

* test(desktop): validate emitted alias metadata structurally

* fix(ghidra): classify bounded Windows endpoint refusals

* test(cli): assert selectable artifact admission recovery

* fix(cli): preserve capture provenance in environment builds
2026-11-10 05:11:20 -05:00

226 lines
7.5 KiB
TypeScript

import type { JavaScriptSemanticPrecisionInput } from "../domain/javascript/javascriptSemanticPrecision.js";
import { fitJavaScriptRouteSourceFacts } from "../domain/javascript/javascriptRouteResultBudget.js";
import { jsonValueByteLength } from "../domain/jsonValueByteLength.js";
import type { JsonValue } from "../domain/jsonValue.js";
import { createHash } from "node:crypto";
import { open } from "node:fs/promises";
import { join } from "node:path";
import { getHeapStatistics } from "node:v8";
import { readBoundedControlLines } from "../process/BoundedControlLines.js";
import { withArtifactFileHandle } from "../artifacts/ArtifactFileOperation.js";
import { ArtifactReaderFailure } from "../artifacts/ArtifactReader.js";
import { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
import { jsonParts } from "../domain/jsonSerialization.js";
import type { IncludedScript } from "../domain/webBundleAnalyzerState.js";
import {
webBundleScriptAnalysisSchema,
type WebBundleScriptAnalysis,
} from "../domain/webBundleScriptAnalysis.js";
import { javaScriptRuntime } from "../domain/runtimeVersion.js";
import { readStableArtifact } from "../artifacts/readStableArtifact.js";
import {
WEB_BUNDLE_WORKER_LIMITS as LIMITS,
includedWebScriptSchema,
webBundleWorkerRequestSchema,
webBundleWorkerMessageSchema,
type WebBundleWorkerMessage,
} from "./WebBundleAnalysisWorkerProtocol.js";
export interface WebBundleScriptAnalyzer {
(
script: IncludedScript,
precision?: JavaScriptSemanticPrecisionInput,
): WebBundleScriptAnalysis;
}
class WebBundleWorkerInputError extends Error {}
class WebBundleWorkerOutputError extends Error {}
const send = async (input: WebBundleWorkerMessage): Promise<void> => {
const value = webBundleWorkerMessageSchema.parse(input);
const frame = `${JSON.stringify(value)}\n`;
if (Buffer.byteLength(frame) > LIMITS.protocolBytes)
throw new RangeError(
"Web bundle worker control frame exceeds its byte budget",
);
await new Promise<void>((resolve, reject) => {
process.stdout.write(frame, (error) =>
error === null || error === undefined ? resolve() : reject(error),
);
});
};
/** Serve sequential script jobs from the private root owned by the worker runtime. */
export const serveWebBundleAnalysisWorker = async (
analyze: WebBundleScriptAnalyzer,
): Promise<void> => {
const root = process.cwd();
const resources = new ArtifactResourceScope();
const requests = readBoundedControlLines(
process.stdin.iterator({ destroyOnReturn: true }),
LIMITS.protocolBytes,
);
await send({
kind: "ready",
heap_limit_bytes:
javaScriptRuntime(process.versions).id === "node"
? getHeapStatistics().heap_size_limit
: null,
});
try {
for (;;) {
const next = await requests.next();
if (next.done) break;
if (!next.value.ok)
throw new RangeError(
"Web bundle worker request exceeds its byte budget",
);
const raw: unknown = JSON.parse(next.value.value);
const request = webBundleWorkerRequestSchema.parse(raw);
const continueServing = await analyzeJob(
root,
request,
analyze,
resources,
);
if (!continueServing) break;
if (
process.memoryUsage().heapUsed >
getHeapStatistics().heap_size_limit / 2
)
global.gc?.();
}
} finally {
await requests.return(undefined);
}
};
const analyzeJob = async (
root: string,
request: ReturnType<typeof webBundleWorkerRequestSchema.parse>,
analyze: WebBundleScriptAnalyzer,
resources: ArtifactResourceScope,
): Promise<boolean> => {
try {
const sourcePath = join(root, `${request.id}.source`);
const source = await readStableArtifact(
sourcePath,
LIMITS.sourceBytes,
undefined,
undefined,
resources,
);
if (
source.bytes.length !== request.source.bytes ||
source.sha256 !== request.source.sha256
)
throw new WebBundleWorkerInputError(
"Web bundle worker source descriptor does not match its file",
);
let script: IncludedScript;
try {
const decoded: unknown = JSON.parse(
new TextDecoder("utf-8", { fatal: true }).decode(source.bytes),
);
script = includedWebScriptSchema.parse(decoded);
} catch (cause) {
throw new WebBundleWorkerInputError(
`Web bundle worker source is not a valid included script: ${cause instanceof Error ? cause.message : String(cause)}`,
);
}
await send({ kind: "phase", id: request.id, phase: "analyze" });
let result: WebBundleScriptAnalysis;
try {
result = webBundleScriptAnalysisSchema.parse(
analyze(script, request.semantic_precision),
);
} catch (cause) {
throw new WebBundleWorkerOutputError(
`Web bundle analyzer returned an invalid result: ${cause instanceof Error ? cause.message : String(cause)}`,
);
}
if (result.status !== "analyzed")
throw new WebBundleWorkerOutputError(
"Web bundle analyzer did not produce a completed per-script result",
);
if (result.route_facts != null) {
const { route_facts, ...existing } = result;
const available =
request.result_budget_bytes -
jsonValueByteLength(
existing as JsonValue,
request.result_budget_bytes,
) -
Buffer.byteLength(',"route_facts":');
const admitted = fitJavaScriptRouteSourceFacts(route_facts, available);
result =
admitted === undefined
? existing
: { ...existing, route_facts: admitted };
}
const resultPath = join(root, `${request.id}.result`);
const file = await open(resultPath, "wx", 0o600);
const hash = createHash("sha256");
let bytes = 0;
await withArtifactFileHandle(
resultPath,
file,
resources,
undefined,
async (handle) => {
for (const part of jsonParts(result)) {
const chunk = Buffer.from(part);
bytes += chunk.length;
if (bytes > request.result_budget_bytes)
throw new RangeError(
"Web bundle worker result exceeds its admitted byte budget",
);
hash.update(chunk);
let offset = 0;
while (offset < chunk.length) {
const written = await handle.write(
chunk,
offset,
chunk.length - offset,
);
if (written.bytesWritten === 0)
throw new Error(
"Web bundle worker result write made no progress",
);
offset += written.bytesWritten;
}
}
},
);
await send({
kind: "complete",
id: request.id,
result: { bytes, sha256: hash.digest("hex") },
});
return true;
} catch (cause: unknown) {
const resource = cause instanceof RangeError;
const input = cause instanceof WebBundleWorkerInputError;
const fatalCleanupFailure =
cause instanceof ArtifactReaderFailure &&
cause.cleanupLeases !== undefined &&
cause.cleanupLeases.length > 0;
const rawMessage = cause instanceof Error ? cause.message : String(cause);
await send({
kind: "failure",
id: request.id,
reason: resource
? "resource"
: input
? "input"
: cause instanceof WebBundleWorkerOutputError ||
cause instanceof TypeError
? "output"
: "io",
message:
rawMessage.length > 2048 ? `${rawMessage.slice(0, 2048)}…` : rawMessage,
});
return !fatalCleanupFailure;
}
};