mirror of
https://github.com/morluto/rea.git
synced 2026-11-03 05:16:16 +00:00
161 lines
5.6 KiB
TypeScript
161 lines
5.6 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import { expect, it } from "vitest";
|
|
import { PeResourcesService } from "./PeResourcesService.js";
|
|
import { PE_RESOURCES_PROVIDER } from "../InvestigationProviders.js";
|
|
import { inspectPeResourcesInputSchema } from "../../domain/native/peResources.js";
|
|
import { parsePeResources } from "../../native/pe/PeResourceParser.js";
|
|
import { peResourceFixture } from "../../native/pe/PeResources.fixture.js";
|
|
import { ok } from "../../domain/result.js";
|
|
import { parseEvidence } from "../../domain/evidence.js";
|
|
import {
|
|
AnalysisCancelledError,
|
|
AnalysisOutputError,
|
|
} from "../../domain/analysisErrorCore.js";
|
|
|
|
const observedReport = () => {
|
|
const { bytes } = peResourceFixture();
|
|
return parsePeResources(
|
|
bytes,
|
|
{
|
|
path: "/fixtures/example.exe",
|
|
bytes: bytes.length,
|
|
sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
},
|
|
inspectPeResourcesInputSchema.parse({ path: "/fixtures/example.exe" }),
|
|
);
|
|
};
|
|
|
|
it.each([
|
|
"different-path",
|
|
"outside-range",
|
|
"wrong-coverage",
|
|
"bad-icon-reference",
|
|
])(
|
|
"rejects invalid provider output before creating Evidence: %s",
|
|
async (problem) => {
|
|
const report = await observedReport();
|
|
if (problem === "different-path") report.artifact.path = "/other.exe";
|
|
if (problem === "outside-range")
|
|
report.resources[0]!.payload.location.offset = report.artifact.bytes;
|
|
if (problem === "wrong-coverage") report.coverage.resources = 99;
|
|
if (problem === "bad-icon-reference")
|
|
report.icon_groups[0]!.images[0]!.candidate_resource_indices = [999];
|
|
const service = new PeResourcesService({
|
|
identity: PE_RESOURCES_PROVIDER,
|
|
inspect: () => Promise.resolve(ok(report)),
|
|
});
|
|
expect(
|
|
await service.inspect({ path: "/fixtures/example.exe" }),
|
|
).toMatchObject({ ok: false, error: { _tag: "AnalysisOutputError" } });
|
|
},
|
|
);
|
|
|
|
it.each([
|
|
["i386 PE32", 0x014c, "pe32", "x86"],
|
|
["AMD64 PE32+", 0x8664, "pe32-plus", "x86_64"],
|
|
["ARM PE32", 0x01c0, "pe32", "arm"],
|
|
["Thumb PE32", 0x01c2, "pe32", "arm"],
|
|
["ARMNT PE32", 0x01c4, "pe32", "arm"],
|
|
["ARM64 PE32+", 0xaa64, "pe32-plus", "arm64"],
|
|
["AMD64 machine with PE32 magic", 0x8664, "pe32", null],
|
|
["i386 machine with PE32+ magic", 0x014c, "pe32-plus", null],
|
|
["ReadyToRun Linux override", 0x8664 ^ 0x7b79, "pe32-plus", null],
|
|
["unknown machine", 0x01f0, "pe32", null],
|
|
] as const)(
|
|
"names the subject architecture from the COFF machine: %s",
|
|
async (_case, machine, format, architecture) => {
|
|
const report = await observedReport();
|
|
report.machine = machine;
|
|
report.format = format;
|
|
const service = new PeResourcesService({
|
|
identity: PE_RESOURCES_PROVIDER,
|
|
inspect: () => Promise.resolve(ok(report)),
|
|
});
|
|
const result = await service.inspect({ path: report.artifact.path });
|
|
if (!result.ok) throw result.error;
|
|
expect(result.value.subject?.architecture ?? null).toBe(architecture);
|
|
},
|
|
);
|
|
|
|
it("retains a completed observation on late cancellation and keeps output errors primary", async () => {
|
|
const report = await observedReport();
|
|
const controller = new AbortController();
|
|
const reason = { request: "late PE cancellation" };
|
|
const service = new PeResourcesService({
|
|
identity: PE_RESOURCES_PROVIDER,
|
|
inspect: () => {
|
|
controller.abort(reason);
|
|
return Promise.resolve(ok(report));
|
|
},
|
|
});
|
|
const late = await service.inspect(
|
|
{ path: report.artifact.path },
|
|
{ signal: controller.signal },
|
|
);
|
|
expect(late).toMatchObject({
|
|
ok: false,
|
|
error: { _tag: "AnalysisCancelledError" },
|
|
});
|
|
if (!late.ok) {
|
|
expect(late.error.cause).toBe(reason);
|
|
expect(parseEvidence(late.error.partialObservation)).toMatchObject({
|
|
operation: "inspect_pe_resources",
|
|
subject: { architecture: "x86" },
|
|
normalized_result: report,
|
|
});
|
|
}
|
|
|
|
const malformed = await observedReport();
|
|
malformed.artifact.path = "/other.exe";
|
|
const outputController = new AbortController();
|
|
const invalid = await new PeResourcesService({
|
|
identity: PE_RESOURCES_PROVIDER,
|
|
inspect: () => {
|
|
outputController.abort();
|
|
return Promise.resolve(ok(malformed));
|
|
},
|
|
}).inspect(
|
|
{ path: "/fixtures/example.exe" },
|
|
{ signal: outputController.signal },
|
|
);
|
|
expect(invalid.ok).toBe(false);
|
|
if (!invalid.ok) expect(invalid.error).toBeInstanceOf(AnalysisOutputError);
|
|
});
|
|
|
|
it.each([
|
|
null,
|
|
"caller cancellation",
|
|
{ request: "opaque caller value" },
|
|
new Error("caller failure"),
|
|
new AnalysisOutputError("caller_operation", "caller-owned cleanup", {
|
|
cleanup: {
|
|
reason: "unresolved caller resource",
|
|
resources: ["caller resource"],
|
|
},
|
|
}),
|
|
])(
|
|
"preserves an opaque admission cancellation reason without invoking the reader: %j",
|
|
async (reason) => {
|
|
const controller = new AbortController();
|
|
controller.abort(reason);
|
|
let calls = 0;
|
|
const service = new PeResourcesService({
|
|
identity: PE_RESOURCES_PROVIDER,
|
|
inspect: async () => {
|
|
calls += 1;
|
|
throw new Error("cancelled admission must not invoke the reader");
|
|
},
|
|
});
|
|
const result = await service.inspect(null, { signal: controller.signal });
|
|
expect(result.ok).toBe(false);
|
|
if (result.ok) throw new Error("expected cancelled admission");
|
|
expect(result.error).toBeInstanceOf(AnalysisCancelledError);
|
|
expect(result.error.cause).toBe(reason);
|
|
expect(result.error.cleanupIncomplete).toBe(false);
|
|
expect(result.error.partialObservation).toBeUndefined();
|
|
expect(calls).toBe(0);
|
|
if (reason instanceof AnalysisOutputError)
|
|
expect(reason.cleanupIncomplete).toBe(true);
|
|
},
|
|
);
|