mirror of
https://github.com/morluto/rea.git
synced 2026-10-12 02:19:24 +00:00
Expose one plan/execute replay contract through both CLI and MCP with exact source, input, runtime, sandbox, limit, and export commitments. Run selected ESM and extracted CommonJS/Rspack factories in a disposable Linux x64 Bubblewrap namespace with a content-addressed Node ELF closure, architecture-checked seccomp, delegated systemd cgroup limits, bounded protocol capture, cancellation, and observable cleanup. Preserve left and right observations as Evidence, derive differential comparisons without erasing unknowns, add optional separately authorized reproducers, and fail closed on stale plans or unavailable host boundaries. Add source-owned parser, sanitizer, clipboard, denial, timeout, OOM, protocol-forgery, and cancellation coverage; a real Linux verifier and workflow; regenerated catalogs and TypeDoc; and updated the packaged REA analysis skill for the 83-tool inventory.
199 lines
5.3 KiB
TypeScript
199 lines
5.3 KiB
TypeScript
import { spawn } from "node:child_process";
|
|
import { readFile } from "node:fs/promises";
|
|
import { resolve } from "node:path";
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
const fixture = async (name: string): Promise<string> =>
|
|
readFile(resolve("tests/fixtures/replay", name), "utf8");
|
|
|
|
const runWorker = async (request: unknown) =>
|
|
new Promise<{ code: number | null; stdout: string; stderr: string }>(
|
|
(resolvePromise, reject) => {
|
|
const child = spawn(
|
|
process.execPath,
|
|
[
|
|
"--experimental-vm-modules",
|
|
resolve("dist/replay/JavaScriptReplayWorker.js"),
|
|
],
|
|
{ stdio: ["pipe", "pipe", "pipe"] },
|
|
);
|
|
let stdout = "";
|
|
let stderr = "";
|
|
child.stdout.on("data", (chunk: Buffer) => {
|
|
stdout += chunk.toString("utf8");
|
|
});
|
|
child.stderr.on("data", (chunk: Buffer) => {
|
|
stderr += chunk.toString("utf8");
|
|
});
|
|
child.once("error", reject);
|
|
child.once("close", (code) => resolvePromise({ code, stdout, stderr }));
|
|
child.stdin.end(JSON.stringify(request));
|
|
},
|
|
);
|
|
|
|
const request = async (
|
|
name: string,
|
|
format: "esm" | "commonjs-factory",
|
|
entryExport: string,
|
|
arguments_: readonly unknown[],
|
|
) => ({
|
|
schemaVersion: 1,
|
|
left: {
|
|
modules: [
|
|
{
|
|
alias: "entry",
|
|
format,
|
|
dependencies: {},
|
|
source: await fixture(name),
|
|
},
|
|
],
|
|
entryAlias: "entry",
|
|
entryExport,
|
|
},
|
|
cases: [
|
|
{
|
|
caseId: "case",
|
|
arguments: arguments_,
|
|
inputSha256: "a".repeat(64),
|
|
},
|
|
],
|
|
determinism: {
|
|
clockIso: "2000-01-01T00:00:00.000Z",
|
|
randomSeed: 7,
|
|
},
|
|
limits: { resultDepth: 16, resultNodes: 10_000, exceptionBytes: 4096 },
|
|
});
|
|
|
|
describe("disposable JavaScript replay worker", () => {
|
|
it("runs an ESM parser with projected plain data", async () => {
|
|
const result = await runWorker(
|
|
await request("parser.mjs", "esm", "default", ["# Title"]),
|
|
);
|
|
expect(result.code).toBe(0);
|
|
expect(JSON.parse(result.stdout)).toMatchObject({
|
|
left: [
|
|
{
|
|
outcome: "return",
|
|
value: { type: "heading", text: "Title" },
|
|
},
|
|
],
|
|
});
|
|
});
|
|
|
|
it("supports extracted Rspack factories and helper-defined exports", async () => {
|
|
const result = await runWorker(
|
|
await request("clipboard.factory.txt", "commonjs-factory", "normalize", [
|
|
"a\r\nb",
|
|
]),
|
|
);
|
|
expect(result.code).toBe(0);
|
|
expect(JSON.parse(result.stdout)).toMatchObject({
|
|
left: [
|
|
{
|
|
outcome: "return",
|
|
value: { text: "a\nb", bytes: 4 },
|
|
},
|
|
],
|
|
});
|
|
});
|
|
|
|
it("runs the source-owned sanitizer fixture", async () => {
|
|
const result = await runWorker(
|
|
await request("sanitizer.factory.txt", "commonjs-factory", "sanitize", [
|
|
"<script>alert(1)</script><b onclick=bad>ok</b>",
|
|
]),
|
|
);
|
|
expect(JSON.parse(result.stdout)).toMatchObject({
|
|
left: [{ outcome: "return", value: "<b>ok</b>" }],
|
|
});
|
|
});
|
|
|
|
it("denies dynamic imports and undeclared requires", async () => {
|
|
const dynamic = await runWorker(
|
|
await request("dynamic-import.mjs", "esm", "default", []),
|
|
);
|
|
expect(JSON.parse(dynamic.stdout)).toMatchObject({
|
|
left: [
|
|
{
|
|
outcome: "denied",
|
|
exception: {
|
|
message: "Dynamic import is unavailable in controlled replay",
|
|
},
|
|
},
|
|
],
|
|
});
|
|
const undeclared = await runWorker(
|
|
await request(
|
|
"undeclared-require.factory.txt",
|
|
"commonjs-factory",
|
|
"default",
|
|
[],
|
|
),
|
|
);
|
|
expect(JSON.parse(undeclared.stdout)).toMatchObject({
|
|
left: [
|
|
{
|
|
outcome: "denied",
|
|
exception: { message: "Undeclared require: node:fs" },
|
|
},
|
|
],
|
|
});
|
|
});
|
|
|
|
it("retains exceptions as observations", async () => {
|
|
const result = await runWorker(
|
|
await request("exception.mjs", "esm", "default", ["value"]),
|
|
);
|
|
expect(result.code).toBe(0);
|
|
expect(JSON.parse(result.stdout)).toMatchObject({
|
|
left: [
|
|
{
|
|
outcome: "exception",
|
|
exception: { name: "TypeError", message: "fixture:value" },
|
|
},
|
|
],
|
|
});
|
|
});
|
|
|
|
it("rejects Proxy results without invoking descriptor traps", async () => {
|
|
const result = await runWorker(
|
|
await request("proxy-result.mjs", "esm", "default", []),
|
|
);
|
|
expect(result.code).toBe(0);
|
|
expect(JSON.parse(result.stdout)).toMatchObject({
|
|
left: [
|
|
{
|
|
outcome: "serialization_error",
|
|
exception: {
|
|
name: "TypeError",
|
|
message: "Proxy replay results are unavailable",
|
|
},
|
|
},
|
|
],
|
|
});
|
|
expect(result.stderr).not.toContain("proxy trap must not run");
|
|
});
|
|
|
|
it("does not expose ambient process, require, network, buffers, or timers", async () => {
|
|
const result = await runWorker(
|
|
await request("side-effect-attempt.mjs", "esm", "default", []),
|
|
);
|
|
expect(result.code).toBe(0);
|
|
expect(JSON.parse(result.stdout)).toMatchObject({
|
|
left: [
|
|
{
|
|
outcome: "return",
|
|
value: {
|
|
process: "undefined",
|
|
require: "undefined",
|
|
fetch: "undefined",
|
|
buffer: "undefined",
|
|
timer: "undefined",
|
|
},
|
|
},
|
|
],
|
|
});
|
|
});
|
|
});
|