713 lines
22 KiB
TypeScript
713 lines
22 KiB
TypeScript
import { createHash } from "node:crypto";
|
|
import { mkdtemp, writeFile } from "node:fs/promises";
|
|
import { tmpdir } from "node:os";
|
|
import { join } from "node:path";
|
|
|
|
import { describe, expect, it } from "vitest";
|
|
|
|
import { runProviderAnalysis } from "../src/application/DirectAnalysis.js";
|
|
import type { AnalysisExecution } from "../src/application/AnalysisProvider.js";
|
|
import {
|
|
managedArtifactInspectionSchema,
|
|
managedMemberInspectionSchema,
|
|
managedNativeBoundaryInspectionSchema,
|
|
} from "../src/domain/managedArtifact.js";
|
|
import type { BinaryTarget } from "../src/domain/binaryTarget.js";
|
|
import { parseBinaryTarget } from "../src/domain/binaryTarget.js";
|
|
import { parseEvidence } from "../src/domain/evidence.js";
|
|
import { ManagedStaticProvider } from "../src/dotnet/ManagedStaticProvider.js";
|
|
import { inspectManagedArtifactBytes } from "../src/dotnet/ManagedArtifactInspector.js";
|
|
import { inspectManagedMembersBytes } from "../src/dotnet/ManagedMemberInspector.js";
|
|
import { inspectManagedNativeBoundariesBytes } from "../src/dotnet/ManagedNativeBoundaryInspector.js";
|
|
import {
|
|
buildManagedPeFixture,
|
|
buildNativePeFixture,
|
|
} from "./fixtures/managedPe.js";
|
|
|
|
const limits = {
|
|
referenceOffset: 0,
|
|
referenceLimit: 100,
|
|
resourceOffset: 0,
|
|
resourceLimit: 100,
|
|
attributeOffset: 0,
|
|
attributeLimit: 100,
|
|
maxMetadataBytes: 1024 * 1024,
|
|
maxTableRows: 1_000,
|
|
maxHeapItemBytes: 1024 * 1024,
|
|
};
|
|
|
|
const memberLimits = {
|
|
typeOffset: 0,
|
|
typeLimit: 100,
|
|
methodOffset: 0,
|
|
methodLimit: 100,
|
|
fieldOffset: 0,
|
|
fieldLimit: 100,
|
|
memberRefOffset: 0,
|
|
memberRefLimit: 100,
|
|
edgeOffset: 0,
|
|
edgeLimit: 100,
|
|
instructionAnchorLimit: 100,
|
|
maxMetadataBytes: 1024 * 1024,
|
|
maxTableRows: 1_000,
|
|
maxHeapItemBytes: 1024 * 1024,
|
|
maxMethodBodyBytes: 1024 * 1024,
|
|
maxMethodInstructions: 1_000,
|
|
};
|
|
|
|
const nativeBoundaryLimits = {
|
|
moduleRefOffset: 0,
|
|
moduleRefLimit: 100,
|
|
importOffset: 0,
|
|
importLimit: 100,
|
|
implementationOffset: 0,
|
|
implementationLimit: 100,
|
|
maxMetadataBytes: 1024 * 1024,
|
|
maxTableRows: 1_000,
|
|
maxHeapItemBytes: 1024 * 1024,
|
|
};
|
|
|
|
describe("managed PE/CLI static provider", () => {
|
|
it("inventories module, assembly, framework, references, and resources without loading CLR code", () => {
|
|
const resource = Buffer.from("source-owned resource");
|
|
const bytes = buildManagedPeFixture({ resourceData: resource });
|
|
const result = inspectManagedArtifactBytes(bytes, target(bytes), limits);
|
|
|
|
expect(result.classification).toMatchObject({
|
|
status: "managed",
|
|
runtime_family: "modern-dotnet",
|
|
implementation: "cil",
|
|
managed_architecture: "anycpu",
|
|
});
|
|
expect(result.pe.cli).toMatchObject({
|
|
flag_names: ["il-only"],
|
|
entry_point: { kind: "metadata-token", value: "0x06000001" },
|
|
});
|
|
expect(result.module).toMatchObject({
|
|
name: "Fixture.dll",
|
|
mvid: "00112233-4455-6677-8899-aabbccddeeff",
|
|
token: "0x00000001",
|
|
});
|
|
expect(result.assembly).toMatchObject({
|
|
name: "Fixture.Managed",
|
|
version: "1.2.3.4",
|
|
token: "0x20000001",
|
|
});
|
|
expect(result.target_frameworks).toEqual([".NETCoreApp,Version=v8.0"]);
|
|
expect(result.references).toMatchObject({
|
|
total: 1,
|
|
returned: 1,
|
|
complete: true,
|
|
items: [expect.objectContaining({ name: "System.Runtime" })],
|
|
});
|
|
expect(result.resources.items).toEqual([
|
|
expect.objectContaining({
|
|
name: "Fixture.resources",
|
|
embedded: true,
|
|
visibility: "private",
|
|
data_length: resource.length,
|
|
data_sha256: createHash("sha256").update(resource).digest("hex"),
|
|
}),
|
|
]);
|
|
expect(result.coverage).toMatchObject({ state: "complete", issues: [] });
|
|
});
|
|
|
|
it("keeps classification evidence complete when caller pages visible references", () => {
|
|
const bytes = buildManagedPeFixture({
|
|
references: ["System.Runtime", "UnityEngine.CoreModule"],
|
|
});
|
|
const result = inspectManagedArtifactBytes(bytes, target(bytes), {
|
|
...limits,
|
|
referenceLimit: 1,
|
|
});
|
|
|
|
expect(result.classification.runtime_family).toBe("unity-mono");
|
|
expect(result.references).toMatchObject({
|
|
total: 2,
|
|
returned: 1,
|
|
complete: false,
|
|
});
|
|
expect(result.coverage.state).toBe("partial");
|
|
expect(result.coverage.issues).toEqual([]);
|
|
});
|
|
|
|
it("accepts CLI metadata GUIDs without RFC UUID version or variant bits", () => {
|
|
const bytes = buildManagedPeFixture({
|
|
mvid: Buffer.from("3aebc60edc4a544b1f458b4ed40b33b1", "hex"),
|
|
});
|
|
const result = inspectManagedArtifactBytes(bytes, target(bytes), limits);
|
|
const members = inspectManagedMembersBytes(
|
|
bytes,
|
|
target(bytes),
|
|
memberLimits,
|
|
);
|
|
const boundaries = inspectManagedNativeBoundariesBytes(
|
|
bytes,
|
|
target(bytes),
|
|
nativeBoundaryLimits,
|
|
);
|
|
|
|
expect(result.module?.mvid).toBe("0ec6eb3a-4adc-4b54-1f45-8b4ed40b33b1");
|
|
expect(result.classification.status).toBe("managed");
|
|
expect(members.identity_scope.requires_mvid).toBe(result.module?.mvid);
|
|
expect(boundaries.identity_scope.requires_mvid).toBe(result.module?.mvid);
|
|
});
|
|
|
|
it("decodes ECMA-335 pointer and byref element types without swapping them", () => {
|
|
const bytes = buildManagedPeFixture({
|
|
fieldSignature: Buffer.from([0x06, 0x1d, 0x0f, 0x0f, 0x08]),
|
|
methodSignature: Buffer.from([
|
|
0x00, 0x02, 0x10, 0x08, 0x0f, 0x08, 0x10, 0x0e,
|
|
]),
|
|
});
|
|
const result = inspectManagedMembersBytes(
|
|
bytes,
|
|
target(bytes),
|
|
memberLimits,
|
|
);
|
|
|
|
expect(result.fields.items[0]?.signature).toMatchObject({
|
|
parse_status: "decoded",
|
|
field_type: "i4**[]",
|
|
});
|
|
expect(result.methods.items[0]?.signature).toMatchObject({
|
|
parse_status: "decoded",
|
|
return_type: "i4&",
|
|
parameter_types: ["i4*", "string&"],
|
|
});
|
|
});
|
|
|
|
it("reads fat method header size from the full flags-and-size word", () => {
|
|
const il = Buffer.from([
|
|
0x21, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x26, 0x22, 0x00,
|
|
0x00, 0x80, 0x3f, 0x26, 0x23, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0,
|
|
0x3f, 0x26, 0xfe, 0x06, 0x01, 0x00, 0x00, 0x06, 0x26, 0x2a,
|
|
]);
|
|
const header = Buffer.alloc(12);
|
|
header.writeUInt16LE(0x3013, 0);
|
|
header.writeUInt16LE(8, 2);
|
|
header.writeUInt32LE(il.length, 4);
|
|
const bytes = buildManagedPeFixture({
|
|
ilBody: Buffer.concat([header, il]),
|
|
});
|
|
const result = inspectManagedMembersBytes(
|
|
bytes,
|
|
target(bytes),
|
|
memberLimits,
|
|
);
|
|
|
|
expect(result.methods.items[0]?.body).toMatchObject({
|
|
status: "present",
|
|
header_format: "fat",
|
|
il_size: il.length,
|
|
il_sha256: createHash("sha256").update(il).digest("hex"),
|
|
opcode_counts: {
|
|
"ldc.i8": 1,
|
|
pop: 4,
|
|
"ldc.r4": 1,
|
|
"ldc.r8": 1,
|
|
ldftn: 1,
|
|
ret: 1,
|
|
},
|
|
issue: null,
|
|
});
|
|
});
|
|
|
|
it("does not normalize reserved CIL opcodes as operand-free instructions", () => {
|
|
const bytes = buildManagedPeFixture({
|
|
ilBody: Buffer.from([0x0a, 0x24, 0x2a]),
|
|
});
|
|
const result = inspectManagedMembersBytes(
|
|
bytes,
|
|
target(bytes),
|
|
memberLimits,
|
|
);
|
|
|
|
expect(result.methods.items[0]?.body).toMatchObject({
|
|
status: "malformed",
|
|
il_size: 2,
|
|
normalized_il_sha256: null,
|
|
decoded_instruction_count: 0,
|
|
issue: "Unsupported CIL opcode 0x24 at IL offset 0",
|
|
});
|
|
});
|
|
|
|
it("keeps the documented decoded-CIL v1 golden vector stable", () => {
|
|
const il = Buffer.from([0x00, 0x2a]);
|
|
const tinyBytes = buildManagedPeFixture({
|
|
ilBody: Buffer.from([0x0a, ...il]),
|
|
});
|
|
const fatHeader = Buffer.alloc(12);
|
|
fatHeader.writeUInt16LE(0x3013, 0);
|
|
fatHeader.writeUInt16LE(32, 2);
|
|
fatHeader.writeUInt32LE(il.length, 4);
|
|
fatHeader.writeUInt32LE(0x1100_0001, 8);
|
|
const fatBytes = buildManagedPeFixture({
|
|
ilBody: Buffer.concat([fatHeader, il]),
|
|
});
|
|
const tiny = inspectManagedMembersBytes(
|
|
tinyBytes,
|
|
target(tinyBytes),
|
|
memberLimits,
|
|
);
|
|
const fat = inspectManagedMembersBytes(
|
|
fatBytes,
|
|
target(fatBytes),
|
|
memberLimits,
|
|
);
|
|
|
|
expect(tiny.methods.items[0]?.body).toMatchObject({
|
|
status: "present",
|
|
header_format: "tiny",
|
|
max_stack: 8,
|
|
init_locals: false,
|
|
local_var_sig_token: null,
|
|
il_size: 2,
|
|
il_sha256: createHash("sha256").update(il).digest("hex"),
|
|
normalized_il_sha256:
|
|
"5e5fad7741cb44bca3a4f045546b7449990da343f612f5f34c0ca30e9eee0636",
|
|
opcode_counts: { nop: 1, ret: 1 },
|
|
exception_regions: [],
|
|
});
|
|
expect(fat.methods.items[0]?.body).toMatchObject({
|
|
header_format: "fat",
|
|
max_stack: 32,
|
|
init_locals: true,
|
|
local_var_sig_token: "0x11000001",
|
|
il_sha256: tiny.methods.items[0]?.body.il_sha256,
|
|
normalized_il_sha256: tiny.methods.items[0]?.body.normalized_il_sha256,
|
|
});
|
|
});
|
|
|
|
it("marks instruction-limited CIL as partial without assigning normalized identity", () => {
|
|
const bytes = buildManagedPeFixture();
|
|
const result = inspectManagedMembersBytes(bytes, target(bytes), {
|
|
...memberLimits,
|
|
maxMethodInstructions: 1,
|
|
});
|
|
const il = bytes.subarray(0x0a01, 0x0a0d);
|
|
|
|
expect(result.metadata.status).toBe("complete");
|
|
expect(result.methods.items[0]?.body).toMatchObject({
|
|
status: "partial",
|
|
il_size: 12,
|
|
il_sha256: createHash("sha256").update(il).digest("hex"),
|
|
normalized_il_sha256: null,
|
|
instruction_count: 1,
|
|
decoded_instruction_count: 1,
|
|
truncated_instructions: 1,
|
|
issue:
|
|
"Instruction decode reached max_method_instructions 1 before the end of the method body",
|
|
});
|
|
expect(result.coverage).toEqual({
|
|
state: "partial",
|
|
issues: [
|
|
{
|
|
code: "limit-exceeded",
|
|
scope: "method.0x06000001.body.instructions",
|
|
offset: 0x0a00,
|
|
detail:
|
|
"Instruction decode reached max_method_instructions 1 before the end of the method body",
|
|
},
|
|
],
|
|
});
|
|
expect(result.limitations.join(" ")).toContain(
|
|
"has no normalized CIL identity",
|
|
);
|
|
});
|
|
|
|
it("inspects metadata members, signatures, CIL hashes, call edges, and field anchors", () => {
|
|
const bytes = buildManagedPeFixture();
|
|
const result = inspectManagedMembersBytes(
|
|
bytes,
|
|
target(bytes),
|
|
memberLimits,
|
|
);
|
|
|
|
expect(result.identity_scope).toEqual({
|
|
token_identity: "build-local",
|
|
requires_artifact_sha256: target(bytes).sha256,
|
|
requires_mvid: "00112233-4455-6677-8899-aabbccddeeff",
|
|
});
|
|
expect(result.types.items).toEqual([
|
|
expect.objectContaining({
|
|
token: "0x02000001",
|
|
full_name: "Fixture.Program",
|
|
field_list: { first_row: 1, last_row: 1, count: 1 },
|
|
method_list: { first_row: 1, last_row: 1, count: 1 },
|
|
}),
|
|
]);
|
|
expect(result.fields.items).toEqual([
|
|
expect.objectContaining({
|
|
token: "0x04000001",
|
|
declaring_type: "Fixture.Program",
|
|
name: "counter",
|
|
signature: expect.objectContaining({
|
|
kind: "field",
|
|
parse_status: "decoded",
|
|
field_type: "i4",
|
|
}),
|
|
}),
|
|
]);
|
|
expect(result.methods.items).toEqual([
|
|
expect.objectContaining({
|
|
token: "0x06000001",
|
|
declaring_type: "Fixture.Program",
|
|
name: "Main",
|
|
rva: 0x2800,
|
|
signature: expect.objectContaining({
|
|
kind: "method",
|
|
parse_status: "decoded",
|
|
return_type: "void",
|
|
parameter_types: [],
|
|
}),
|
|
body: expect.objectContaining({
|
|
status: "present",
|
|
header_format: "tiny",
|
|
file_offset: 0x0a00,
|
|
il_size: 12,
|
|
instruction_count: 4,
|
|
decoded_instruction_count: 4,
|
|
truncated_instructions: 0,
|
|
opcode_counts: { "ldarg.0": 1, ldfld: 1, call: 1, ret: 1 },
|
|
anchors: [
|
|
{
|
|
il_offset: 1,
|
|
opcode: "ldfld",
|
|
operand_kind: "field",
|
|
operand: "0x04000001",
|
|
},
|
|
{
|
|
il_offset: 6,
|
|
opcode: "call",
|
|
operand_kind: "method",
|
|
operand: "0x0a000001",
|
|
},
|
|
],
|
|
}),
|
|
}),
|
|
]);
|
|
expect(result.member_refs.items).toEqual([
|
|
expect.objectContaining({
|
|
token: "0x0a000001",
|
|
name: ".ctor",
|
|
signature: expect.objectContaining({
|
|
kind: "method",
|
|
parse_status: "decoded",
|
|
parameter_types: ["string"],
|
|
}),
|
|
}),
|
|
]);
|
|
expect(result.call_edges.items).toEqual([
|
|
{
|
|
caller_token: "0x06000001",
|
|
caller: "Fixture.Program.Main",
|
|
opcode: "call",
|
|
target_token: "0x0a000001",
|
|
target_kind: "member-ref",
|
|
target_name: ".ctor",
|
|
},
|
|
]);
|
|
expect(result.field_accesses.items).toEqual([
|
|
{
|
|
method_token: "0x06000001",
|
|
method: "Fixture.Program.Main",
|
|
opcode: "ldfld",
|
|
field_token: "0x04000001",
|
|
field_name: "counter",
|
|
},
|
|
]);
|
|
expect(result.coverage).toMatchObject({ state: "complete", issues: [] });
|
|
});
|
|
|
|
it("inspects managed/native PInvoke declarations without verifying native exports", () => {
|
|
const bytes = buildManagedPeFixture({
|
|
pinvoke: {
|
|
moduleName: "user32.dll",
|
|
importName: "MessageBoxW",
|
|
mappingFlags: 0x0345,
|
|
},
|
|
readyToRun: true,
|
|
});
|
|
const result = inspectManagedNativeBoundariesBytes(
|
|
bytes,
|
|
target(bytes),
|
|
nativeBoundaryLimits,
|
|
);
|
|
|
|
expect(result.cli_native).toMatchObject({
|
|
il_only: true,
|
|
ready_to_run_signature: true,
|
|
managed_native_header_rva: 0x2700,
|
|
managed_native_header_size: 4,
|
|
});
|
|
expect(result.module_refs.items).toEqual([
|
|
{
|
|
token: "0x1a000001",
|
|
row_offset: expect.any(Number),
|
|
name: "user32.dll",
|
|
},
|
|
]);
|
|
expect(result.pinvoke_imports.items).toEqual([
|
|
expect.objectContaining({
|
|
token: "0x1c000001",
|
|
member_token: "0x06000001",
|
|
member_kind: "method",
|
|
member_name: "Main",
|
|
import_name: "MessageBoxW",
|
|
import_scope_token: "0x1a000001",
|
|
import_scope_name: "user32.dll",
|
|
no_mangle: true,
|
|
char_set: "unicode",
|
|
call_convention: "stdcall",
|
|
supports_last_error: true,
|
|
verification: "managed-declaration-only",
|
|
}),
|
|
]);
|
|
expect(result.native_implementations.items).toEqual([
|
|
expect.objectContaining({
|
|
token: "0x06000001",
|
|
name: "Main",
|
|
pinvoke_declared: true,
|
|
boundary_kind: "pinvoke",
|
|
body_interpretation: "native-or-runtime",
|
|
}),
|
|
]);
|
|
expect(result.summary).toMatchObject({
|
|
module_ref_count: 1,
|
|
pinvoke_import_count: 1,
|
|
native_implementation_count: 1,
|
|
ready_to_run: true,
|
|
mixed_mode_or_native_header: true,
|
|
});
|
|
expect(result.limitations).toContain(
|
|
"P/Invoke rows prove managed import declarations only; this inspection does not verify that a native library, export, thunk, or provider-qualified function exists.",
|
|
);
|
|
});
|
|
|
|
it("keeps member evidence bounded and typed for pagination and unavailable metadata", () => {
|
|
const bytes = buildManagedPeFixture();
|
|
const paged = inspectManagedMembersBytes(bytes, target(bytes), {
|
|
...memberLimits,
|
|
methodLimit: 0 + 1,
|
|
instructionAnchorLimit: 1,
|
|
});
|
|
expect(paged.methods).toMatchObject({
|
|
total: 1,
|
|
returned: 1,
|
|
complete: true,
|
|
});
|
|
expect(paged.methods.items[0]?.body.anchors).toHaveLength(1);
|
|
|
|
const nativeBytes = buildNativePeFixture();
|
|
const native = inspectManagedMembersBytes(
|
|
nativeBytes,
|
|
target(nativeBytes),
|
|
memberLimits,
|
|
);
|
|
expect(native.metadata.status).toBe("absent");
|
|
expect(native.coverage.state).toBe("unavailable");
|
|
|
|
const malformedBytes = buildManagedPeFixture({
|
|
corruptMetadataSignature: true,
|
|
});
|
|
const malformed = inspectManagedMembersBytes(
|
|
malformedBytes,
|
|
target(malformedBytes),
|
|
memberLimits,
|
|
);
|
|
expect(malformed.metadata.status).toBe("malformed");
|
|
expect(malformed.coverage.issues).toEqual([
|
|
expect.objectContaining({ code: "invalid-metadata-root" }),
|
|
]);
|
|
});
|
|
|
|
it("reports native PE, malformed metadata, and bounded metadata failures as typed results", () => {
|
|
const native = inspectManagedArtifactBytes(
|
|
buildNativePeFixture(),
|
|
target(buildNativePeFixture()),
|
|
limits,
|
|
);
|
|
expect(native.classification.status).toBe("not-managed");
|
|
expect(native.metadata.status).toBe("absent");
|
|
expect(native.coverage.state).toBe("unavailable");
|
|
|
|
const malformedBytes = buildManagedPeFixture({
|
|
corruptMetadataSignature: true,
|
|
});
|
|
const malformed = inspectManagedArtifactBytes(
|
|
malformedBytes,
|
|
target(malformedBytes),
|
|
limits,
|
|
);
|
|
expect(malformed.classification.status).toBe("malformed");
|
|
expect(malformed.coverage.issues).toEqual([
|
|
expect.objectContaining({ code: "invalid-metadata-root" }),
|
|
]);
|
|
|
|
const unsupportedTableBytes = buildManagedPeFixture({
|
|
metadataValidMaskExtra: 1n << 50n,
|
|
});
|
|
const unsupportedTable = inspectManagedArtifactBytes(
|
|
unsupportedTableBytes,
|
|
target(unsupportedTableBytes),
|
|
limits,
|
|
);
|
|
expect(unsupportedTable.coverage.issues).toEqual([
|
|
expect.objectContaining({ code: "invalid-tables" }),
|
|
]);
|
|
|
|
const limited = inspectManagedArtifactBytes(
|
|
buildManagedPeFixture(),
|
|
target(buildManagedPeFixture()),
|
|
{ ...limits, maxMetadataBytes: 256 },
|
|
);
|
|
expect(limited.metadata.status).toBe("partial");
|
|
expect(limited.coverage.issues).toEqual([
|
|
expect.objectContaining({ code: "limit-exceeded" }),
|
|
]);
|
|
});
|
|
|
|
it("executes as a read-only auxiliary provider with digest, cancellation, and format boundaries", async () => {
|
|
const directory = await mkdtemp(join(tmpdir(), "rea-managed-provider-"));
|
|
const bytes = buildManagedPeFixture();
|
|
const path = join(directory, "fixture.exe");
|
|
await writeFile(path, bytes);
|
|
const parsed = await parseBinaryTarget(path);
|
|
expect(parsed.ok).toBe(true);
|
|
if (!parsed.ok) return;
|
|
|
|
const client = new ManagedStaticProvider().createClient(parsed.value);
|
|
const observed = await client.execute("inspect_managed_artifact", {});
|
|
expect(observed.ok).toBe(true);
|
|
if (!observed.ok) return;
|
|
expect(asManagedResult(observed.value)).toMatchObject({
|
|
classification: { status: "managed", runtime_family: "modern-dotnet" },
|
|
artifact: { path, sha256: parsed.value.sha256, format: "pe" },
|
|
});
|
|
expect(observed.value.provider.id).toBe("rea-dotnet-static");
|
|
expect(observed.value.subject).toMatchObject({ path, format: "pe" });
|
|
|
|
const members = await client.execute("inspect_managed_members", {});
|
|
expect(members.ok).toBe(true);
|
|
if (!members.ok) return;
|
|
expect(asManagedMemberResult(members.value)).toMatchObject({
|
|
artifact: { path, sha256: parsed.value.sha256, format: "pe" },
|
|
methods: { total: 1 },
|
|
call_edges: { total: 1 },
|
|
field_accesses: { total: 1 },
|
|
});
|
|
|
|
const boundaries = await client.execute(
|
|
"inspect_managed_native_boundaries",
|
|
{},
|
|
);
|
|
expect(boundaries.ok).toBe(true);
|
|
if (!boundaries.ok) return;
|
|
expect(asManagedNativeBoundaryResult(boundaries.value)).toMatchObject({
|
|
artifact: { path, sha256: parsed.value.sha256, format: "pe" },
|
|
module_refs: { total: 0 },
|
|
pinvoke_imports: { total: 0 },
|
|
native_implementations: { total: 0 },
|
|
});
|
|
|
|
const cancelled = new AbortController();
|
|
cancelled.abort();
|
|
const cancelledResult = await client.execute(
|
|
"inspect_managed_artifact",
|
|
{},
|
|
{ signal: cancelled.signal },
|
|
);
|
|
expect(cancelledResult).toMatchObject({
|
|
ok: false,
|
|
error: { _tag: "AnalysisCancelledError" },
|
|
});
|
|
|
|
const changed = Buffer.from(bytes);
|
|
changed[0x300] = 0;
|
|
await writeFile(path, changed);
|
|
const staleDigest = await client.execute("inspect_managed_artifact", {});
|
|
expect(staleDigest).toMatchObject({
|
|
ok: false,
|
|
error: { _tag: "EvidenceIntegrityError" },
|
|
});
|
|
|
|
await client.close();
|
|
const nativeClient = new ManagedStaticProvider().createClient({
|
|
...parsed.value,
|
|
format: "elf",
|
|
});
|
|
await expect(
|
|
nativeClient.execute("inspect_managed_artifact", {}),
|
|
).resolves.toMatchObject({
|
|
ok: false,
|
|
error: { _tag: "AnalysisCapabilityUnavailableError" },
|
|
});
|
|
});
|
|
|
|
it("has the same evidence shape through direct CLI analysis plumbing", async () => {
|
|
const directory = await mkdtemp(join(tmpdir(), "rea-managed-cli-"));
|
|
const path = join(directory, "fixture.exe");
|
|
await writeFile(path, buildManagedPeFixture());
|
|
|
|
const evidence = parseEvidence(
|
|
await runProviderAnalysis(path, "inspect_managed_artifact", {
|
|
reference_limit: 1,
|
|
}),
|
|
);
|
|
expect(evidence).toMatchObject({
|
|
operation: "inspect_managed_artifact",
|
|
provider: { id: "rea-dotnet-static" },
|
|
subject: { local_path: path, format: "pe" },
|
|
normalized_result: {
|
|
classification: { status: "managed", runtime_family: "modern-dotnet" },
|
|
references: { limit: 1 },
|
|
},
|
|
});
|
|
|
|
const memberEvidence = parseEvidence(
|
|
await runProviderAnalysis(path, "inspect_managed_members", {}),
|
|
);
|
|
expect(memberEvidence).toMatchObject({
|
|
operation: "inspect_managed_members",
|
|
provider: { id: "rea-dotnet-static" },
|
|
subject: { local_path: path, format: "pe" },
|
|
normalized_result: {
|
|
identity_scope: { token_identity: "build-local" },
|
|
methods: { total: 1 },
|
|
},
|
|
});
|
|
|
|
const boundaryEvidence = parseEvidence(
|
|
await runProviderAnalysis(path, "inspect_managed_native_boundaries", {}),
|
|
);
|
|
expect(boundaryEvidence).toMatchObject({
|
|
operation: "inspect_managed_native_boundaries",
|
|
provider: { id: "rea-dotnet-static" },
|
|
subject: { local_path: path, format: "pe" },
|
|
normalized_result: {
|
|
identity_scope: { token_identity: "build-local" },
|
|
pinvoke_imports: { total: 0 },
|
|
},
|
|
});
|
|
});
|
|
});
|
|
|
|
const target = (bytes: Buffer): BinaryTarget => ({
|
|
path: "/fixture.exe",
|
|
sha256: createHash("sha256").update(bytes).digest("hex"),
|
|
kind: "executable",
|
|
format: "pe",
|
|
architecture: "x86",
|
|
availableArchitectures: ["x86"],
|
|
});
|
|
|
|
const asManagedResult = (execution: AnalysisExecution) =>
|
|
managedArtifactInspectionSchema.parse(execution.result);
|
|
|
|
const asManagedMemberResult = (execution: AnalysisExecution) =>
|
|
managedMemberInspectionSchema.parse(execution.result);
|
|
|
|
const asManagedNativeBoundaryResult = (execution: AnalysisExecution) =>
|
|
managedNativeBoundaryInspectionSchema.parse(execution.result);
|