mirror of
https://github.com/morluto/rea.git
synced 2026-11-02 21:06:04 +00:00
324 lines
11 KiB
TypeScript
324 lines
11 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import {
|
|
access,
|
|
chmod,
|
|
mkdir,
|
|
readFile,
|
|
readdir,
|
|
symlink,
|
|
writeFile,
|
|
} from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import { expect } from "vitest";
|
|
import { z } from "zod";
|
|
|
|
import { analysisErrorProjectionSchema } from "../../../src/contracts/errorSchemas.js";
|
|
import { parseEvidence } from "../../../src/domain/evidence.js";
|
|
import { wasmArtifactSchema } from "../../../src/domain/wasm/wasmArtifact.js";
|
|
import { observeProcessStartIdentity } from "../../../src/process/ProcessOwnershipObservation.js";
|
|
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
|
import { cliTest } from "../../support/cli/cliFixture.js";
|
|
|
|
const tools = ["wasm-validate", "wasm-objdump", "wasm2wat"] as const;
|
|
const commandTraceSchema = z.object({
|
|
tool: z.enum(tools),
|
|
pid: z.int().positive(),
|
|
cwd: z.string(),
|
|
run_id: z.string(),
|
|
version: z.boolean(),
|
|
snapshot_sha256: z.string().nullable(),
|
|
});
|
|
const fixtureUrl = (name: string) =>
|
|
fileURLToPath(new URL(`../../fixtures/wasm/${name}`, import.meta.url));
|
|
|
|
/** Real shell/Node launchers with authored WABT output, not installed WABT engines. */
|
|
const createFixture = async () => {
|
|
const root = await createTestTempDirectory("rea-wabt-launcher-");
|
|
const artifact = join(root, "selected module.wasm");
|
|
const hold = join(root, "hold-decoder");
|
|
const trace = join(root, "commands.jsonl");
|
|
const scratch = join(root, "scratch");
|
|
const runner = join(root, "wabt-runner.cjs");
|
|
await mkdir(scratch);
|
|
const bytes = Buffer.from(
|
|
"0061736d01000000010401600000020b0103656e7603666f6f0000030201000503010001071002066d656d6f727902000372756e00010a0601040001010b000c04746573747061796c6f6164",
|
|
"hex",
|
|
);
|
|
await writeFile(artifact, bytes);
|
|
await writeFile(
|
|
runner,
|
|
`
|
|
const fs = require("node:fs");
|
|
const tool = process.argv[2];
|
|
const version = process.argv[3] === "--version";
|
|
const snapshot_sha256 = version ? null : require("node:crypto").createHash("sha256").update(fs.readFileSync("module.wasm")).digest("hex");
|
|
fs.appendFileSync(process.env.REA_WABT_TEST_TRACE, JSON.stringify({ tool, pid: process.pid, version, cwd: process.cwd(), run_id: process.env.REA_PROCESS_RUN_ID, snapshot_sha256 }) + "\\n");
|
|
if (version) {
|
|
process.stdout.write("1.0.42\\n");
|
|
if (tool === "wasm-validate" && process.env.REA_WABT_TEST_REVOKE_COMMAND)
|
|
fs.chmodSync(process.env.REA_WABT_TEST_REVOKE_COMMAND, 0);
|
|
}
|
|
else if (tool === "wasm-objdump") { process.stdout.write(fs.readFileSync(process.env.REA_WABT_TEST_DUMP)); }
|
|
else if (tool === "wasm2wat") {
|
|
const decode = () => process.stdout.write(fs.readFileSync(process.env.REA_WABT_TEST_WAT));
|
|
if (fs.existsSync(process.env.REA_WABT_TEST_HOLD)) {
|
|
setTimeout(decode, 4000);
|
|
} else { decode(); }
|
|
}
|
|
`,
|
|
);
|
|
const install = async (directory: string, decoy = false) => {
|
|
await mkdir(directory, { recursive: true });
|
|
for (const tool of tools)
|
|
await writeFile(
|
|
join(directory, tool),
|
|
decoy
|
|
? "#!/bin/sh\nprintf 'unselected-decoy\\n'\n"
|
|
: `#!/bin/sh\nexec "$REA_WABT_TEST_NODE" "$REA_WABT_TEST_RUNNER" "${tool}" "$@"\n`,
|
|
{ mode: 0o700 },
|
|
);
|
|
};
|
|
const environment = {
|
|
...process.env,
|
|
REA_WABT_TEST_NODE: process.execPath,
|
|
REA_WABT_TEST_RUNNER: runner,
|
|
REA_WABT_TEST_DUMP: fixtureUrl("objdump.txt"),
|
|
REA_WABT_TEST_WAT: fixtureUrl("decoded.wat"),
|
|
REA_WABT_TEST_HOLD: hold,
|
|
REA_WABT_TEST_TRACE: trace,
|
|
TMPDIR: scratch,
|
|
TMP: scratch,
|
|
TEMP: scratch,
|
|
};
|
|
return {
|
|
root,
|
|
artifact,
|
|
bytes,
|
|
hold,
|
|
trace,
|
|
scratch,
|
|
install,
|
|
environment,
|
|
};
|
|
};
|
|
|
|
cliTest.skipIf(process.platform === "win32")(
|
|
"cleans up real selected WABT interpreter shims after a command deadline",
|
|
async ({ cli }) => {
|
|
const fixture = await createFixture();
|
|
const selected = join(fixture.root, "selected tools");
|
|
await fixture.install(selected);
|
|
const invoke = (timeout: number) =>
|
|
cli.run({
|
|
arguments: [
|
|
"inspect-wasm-artifact",
|
|
fixture.artifact,
|
|
"--command-timeout-ms",
|
|
String(timeout),
|
|
"--json",
|
|
],
|
|
environment: {
|
|
...fixture.environment,
|
|
REA_WABT_BIN_DIRECTORY: selected,
|
|
},
|
|
});
|
|
const control = await invoke(10_000);
|
|
expect(control.exitCode, `${control.stderr}\n${control.stdout}`).toBe(0);
|
|
const evidence = parseEvidence(control.json);
|
|
const report = wasmArtifactSchema.parse(evidence.normalized_result);
|
|
expect(report.artifact.sha256).toBe(
|
|
createHash("sha256").update(fixture.bytes).digest("hex"),
|
|
);
|
|
expect(report.validation).toBe("valid");
|
|
const trace = (await readFile(fixture.trace, "utf8"))
|
|
.trim()
|
|
.split("\n")
|
|
.map((line) => commandTraceSchema.parse(JSON.parse(line)));
|
|
expect(trace.length).toBeGreaterThan(0);
|
|
expect(new Set(trace.map(({ cwd }) => cwd)).size).toBe(1);
|
|
for (const command of trace) {
|
|
expect(command.cwd.startsWith(join(fixture.scratch, "rea-wabt-"))).toBe(
|
|
true,
|
|
);
|
|
expect(command.run_id).toMatch(/^rea-wabt-/u);
|
|
expect(command.snapshot_sha256).toBe(
|
|
command.version ? null : report.artifact.sha256,
|
|
);
|
|
}
|
|
expect(await readdir(fixture.scratch)).toEqual([]);
|
|
|
|
await writeFile(fixture.hold, "hold this invocation");
|
|
try {
|
|
const invocation = await invoke(2_000);
|
|
expect(invocation.exitCode, invocation.stderr).toBe(1);
|
|
const failure = analysisErrorProjectionSchema.parse(invocation.json);
|
|
expect(failure, invocation.stdout).toMatchObject({
|
|
code: "provider_timeout",
|
|
details: {
|
|
partial_observation: {
|
|
normalized_result: {
|
|
validation: "valid",
|
|
artifact: { sha256: report.artifact.sha256 },
|
|
},
|
|
},
|
|
},
|
|
});
|
|
expect(await readdir(fixture.scratch)).toEqual([]);
|
|
expect((await readFile(fixture.artifact)).equals(fixture.bytes)).toBe(
|
|
true,
|
|
);
|
|
} finally {
|
|
// The authored shim self-exits even on the unfixed cleanup-refusal path.
|
|
// Observe its actual process lifetime; token-owned cleanup can use SIGKILL.
|
|
const commands = (await readFile(fixture.trace, "utf8"))
|
|
.trim()
|
|
.split("\n")
|
|
.map((line) => commandTraceSchema.parse(JSON.parse(line)));
|
|
const decoder = commands.findLast(
|
|
(command) => command.tool === "wasm2wat" && !command.version,
|
|
);
|
|
if (decoder === undefined)
|
|
throw new Error("No decoder process was observed");
|
|
await expect
|
|
.poll(
|
|
() =>
|
|
observeProcessStartIdentity(decoder.pid, undefined, decoder.run_id),
|
|
{ timeout: 6000 },
|
|
)
|
|
.toBeUndefined();
|
|
}
|
|
},
|
|
);
|
|
|
|
cliTest.skipIf(process.platform === "win32" || process.getuid?.() === 0)(
|
|
"retains completed WABT version output when its post-command fingerprint read is denied",
|
|
async ({ cli }) => {
|
|
const fixture = await createFixture();
|
|
const selected = join(fixture.root, "tools");
|
|
await fixture.install(selected);
|
|
const revoked = join(selected, "wasm-validate");
|
|
try {
|
|
const invocation = await cli.run({
|
|
arguments: ["inspect-wasm-artifact", fixture.artifact, "--json"],
|
|
environment: {
|
|
...fixture.environment,
|
|
REA_WABT_BIN_DIRECTORY: selected,
|
|
REA_WABT_TEST_REVOKE_COMMAND: revoked,
|
|
},
|
|
});
|
|
expect(invocation.exitCode, invocation.stderr).toBe(1);
|
|
const failure = analysisErrorProjectionSchema.parse(invocation.json);
|
|
expect(failure, invocation.stdout).toMatchObject({
|
|
code: "capability_unavailable",
|
|
details: {
|
|
captured_output: {
|
|
stdout: "1.0.42\n",
|
|
stderr: "",
|
|
truncated: false,
|
|
exit_code: 0,
|
|
signal: null,
|
|
},
|
|
},
|
|
});
|
|
expect((await readFile(fixture.artifact)).equals(fixture.bytes)).toBe(
|
|
true,
|
|
);
|
|
expect(await readdir(fixture.scratch)).toEqual([]);
|
|
} finally {
|
|
await chmod(revoked, 0o700);
|
|
}
|
|
},
|
|
);
|
|
|
|
cliTest.skipIf(process.platform === "win32")(
|
|
"launches selected WABT symlinks with their own sibling resources and fingerprints canonical tools",
|
|
async ({ cli }) => {
|
|
const fixture = await createFixture();
|
|
const targets = join(fixture.root, "canonical tools");
|
|
const selected = join(fixture.root, "selected links");
|
|
await fixture.install(targets);
|
|
await mkdir(selected);
|
|
await writeFile(
|
|
join(selected, "wabt-environment"),
|
|
"# selected installation resource\n",
|
|
);
|
|
for (const tool of tools) {
|
|
const target = join(targets, tool);
|
|
const script = await readFile(target, "utf8");
|
|
await writeFile(
|
|
target,
|
|
script.replace(
|
|
"#!/bin/sh\n",
|
|
'#!/bin/sh\n. "${0%/*}/wabt-environment"\n',
|
|
),
|
|
);
|
|
await symlink(target, join(selected, tool));
|
|
}
|
|
const invocation = await cli.run({
|
|
arguments: ["inspect-wasm-artifact", fixture.artifact, "--json"],
|
|
environment: { ...fixture.environment, REA_WABT_BIN_DIRECTORY: selected },
|
|
});
|
|
expect(
|
|
invocation.exitCode,
|
|
`${invocation.stderr}\n${invocation.stdout}`,
|
|
).toBe(0);
|
|
const report = wasmArtifactSchema.parse(
|
|
parseEvidence(invocation.json).normalized_result,
|
|
);
|
|
expect(report.tool_profile.commands.map(({ path }) => path)).toEqual(
|
|
tools.map((tool) => join(targets, tool)),
|
|
);
|
|
for (const command of report.tool_profile.commands)
|
|
expect(command.sha256).toBe(
|
|
createHash("sha256")
|
|
.update(await readFile(command.path))
|
|
.digest("hex"),
|
|
);
|
|
expect((await readFile(fixture.artifact)).equals(fixture.bytes)).toBe(true);
|
|
expect(await readdir(fixture.scratch)).toEqual([]);
|
|
},
|
|
);
|
|
|
|
cliTest.skipIf(process.platform === "win32")(
|
|
"selects a WABT directory through literal symlink and parent traversal",
|
|
async ({ cli }) => {
|
|
const fixture = await createFixture();
|
|
const intended = join(fixture.root, "intended");
|
|
await mkdir(join(intended, "nested"), { recursive: true });
|
|
await symlink(join(intended, "nested"), join(fixture.root, "alias"));
|
|
const selected = join(intended, "tools");
|
|
await fixture.install(selected);
|
|
await fixture.install(join(fixture.root, "tools"), true);
|
|
const invocation = await cli.run({
|
|
arguments: ["inspect-wasm-artifact", fixture.artifact, "--json"],
|
|
environment: {
|
|
...fixture.environment,
|
|
REA_WABT_BIN_DIRECTORY: `${fixture.root}/alias/../tools`,
|
|
},
|
|
});
|
|
expect(
|
|
invocation.exitCode,
|
|
`${invocation.stderr}\n${invocation.stdout}`,
|
|
).toBe(0);
|
|
const report = wasmArtifactSchema.parse(
|
|
parseEvidence(invocation.json).normalized_result,
|
|
);
|
|
expect(report.tool_profile.commands.map(({ path }) => path)).toEqual(
|
|
tools.map((tool) => join(selected, tool)),
|
|
);
|
|
for (const command of report.tool_profile.commands)
|
|
expect(command.sha256).toBe(
|
|
createHash("sha256")
|
|
.update(await readFile(command.path))
|
|
.digest("hex"),
|
|
);
|
|
expect(report.artifact.path).toBe(fixture.artifact);
|
|
expect((await readFile(fixture.artifact)).equals(fixture.bytes)).toBe(true);
|
|
expect(await readdir(fixture.scratch)).toEqual([]);
|
|
await expect(
|
|
access(join(fixture.root, "tools", "wasm-validate")),
|
|
).resolves.toBeUndefined();
|
|
},
|
|
);
|