220 lines
7.8 KiB
TypeScript
220 lines
7.8 KiB
TypeScript
import { describe, expect, it } from "vitest";
|
|
|
|
import { projectManagedApplicationGraphEvidence } from "../../../src/application/managed/ManagedApplicationGraphService.js";
|
|
import { managedApplicationGraphReferenceInputSchema } from "../../../src/contracts/managed/managedWorkflowToolContracts.js";
|
|
import { MANAGED_APPLICATION_GRAPH_EXAMPLE } from "../../../src/contracts/managed/managedWorkflowExamples.js";
|
|
import { traceApplicationFeatureEvidence } from "../../../src/application/javascript/JavaScriptApplicationWorkflowService.js";
|
|
import { MANAGED_STATIC_PROVIDER } from "../../../src/application/InvestigationProviders.js";
|
|
import { createEvidence, parseEvidence } from "../../../src/domain/evidence.js";
|
|
import { parseJavaScriptApplicationGraph } from "../../../src/domain/javascript/javascriptApplicationGraph.js";
|
|
import { managedApplicationGraphResultSchema } from "../../../src/domain/managed/managedApplicationGraph.js";
|
|
import { inspectManagedArtifactBytes } from "../../../src/dotnet/ManagedArtifactInspector.js";
|
|
import { inspectManagedMembersBytes } from "../../../src/dotnet/ManagedMemberInspector.js";
|
|
import { inspectManagedNativeBoundariesBytes } from "../../../src/dotnet/ManagedNativeBoundaryInspector.js";
|
|
import {
|
|
buildManagedPeFixture,
|
|
managedPeFixtureTarget,
|
|
} from "../../../src/dotnet/ManagedPe.fixture.js";
|
|
|
|
describe("managed application graph projection", () => {
|
|
it("projects managed metadata and native declarations into authenticated graph Evidence", () => {
|
|
const bytes = buildManagedPeFixture({
|
|
pinvoke: {
|
|
moduleName: "user32.dll",
|
|
importName: "MessageBoxW",
|
|
mappingFlags: 0x0345,
|
|
},
|
|
});
|
|
const binary = managedPeFixtureTarget(bytes, "/fixture/ManagedInterop.exe");
|
|
const managedArtifact = inspectManagedArtifactBytes(bytes, binary);
|
|
const members = inspectManagedMembersBytes(bytes, binary);
|
|
const boundaries = inspectManagedNativeBoundariesBytes(bytes, binary);
|
|
const artifactEvidence = createEvidence(binary, MANAGED_STATIC_PROVIDER, {
|
|
operation: "inspect_managed_artifact",
|
|
parameters: {},
|
|
result: managedArtifact,
|
|
rawResult: null,
|
|
limitations: managedArtifact.limitations,
|
|
locations: [{ kind: "artifact-path", path: binary.path }],
|
|
});
|
|
const memberEvidence = createEvidence(binary, MANAGED_STATIC_PROVIDER, {
|
|
operation: "inspect_managed_members",
|
|
parameters: {},
|
|
result: members,
|
|
rawResult: null,
|
|
limitations: members.limitations,
|
|
locations: [{ kind: "artifact-path", path: binary.path }],
|
|
});
|
|
const boundaryEvidence = createEvidence(binary, MANAGED_STATIC_PROVIDER, {
|
|
operation: "inspect_managed_native_boundaries",
|
|
parameters: {},
|
|
result: boundaries,
|
|
rawResult: null,
|
|
limitations: boundaries.limitations,
|
|
locations: [{ kind: "artifact-path", path: binary.path }],
|
|
});
|
|
|
|
const evidence = projectManagedApplicationGraphEvidence({
|
|
managed_artifact: artifactEvidence,
|
|
managed_members: memberEvidence,
|
|
managed_native_boundaries: boundaryEvidence,
|
|
});
|
|
|
|
expect(
|
|
evidence.ok,
|
|
evidence.ok
|
|
? undefined
|
|
: `${JSON.stringify(evidence.error)} cause=${String(evidence.error.cause)}`,
|
|
).toBe(true);
|
|
if (!evidence.ok) throw new Error("projection failed");
|
|
const parsed = parseEvidence(evidence.value);
|
|
expect(parsed).toMatchObject({
|
|
operation: "project_managed_application_graph",
|
|
predicate_type: "rea.managed-application-graph",
|
|
provider: { id: "rea-dotnet-workflows" },
|
|
confidence: "inferred",
|
|
authority: "analyst-inference",
|
|
evidence_links: [
|
|
artifactEvidence.evidence_id,
|
|
memberEvidence.evidence_id,
|
|
boundaryEvidence.evidence_id,
|
|
],
|
|
});
|
|
const result = managedApplicationGraphResultSchema.parse(
|
|
parsed.normalized_result,
|
|
);
|
|
const graph = parseJavaScriptApplicationGraph(result.graph);
|
|
expect(result.summary).toMatchObject({
|
|
assemblies: 1,
|
|
modules: 1,
|
|
types: 1,
|
|
methods: 1,
|
|
fields: 1,
|
|
pinvoke_imports: 1,
|
|
});
|
|
expect(graph.nodes.map(({ kind }) => kind)).toEqual(
|
|
expect.arrayContaining([
|
|
"artifact",
|
|
"managed-assembly",
|
|
"managed-module",
|
|
"managed-type",
|
|
"managed-method",
|
|
"managed-field",
|
|
"managed-pinvoke-import",
|
|
]),
|
|
);
|
|
const method = graph.nodes.find(
|
|
({ kind, observations }) =>
|
|
kind === "managed-method" &&
|
|
observations[0]?.label === "Fixture.Program.Main",
|
|
);
|
|
expect(method).toBeDefined();
|
|
expect(
|
|
graph.edges.some(
|
|
({ source_node_id, relation, target_node_id }) =>
|
|
source_node_id === method?.node_id &&
|
|
relation === "imports" &&
|
|
graph.nodes.find(({ node_id: id }) => id === target_node_id)?.kind ===
|
|
"managed-pinvoke-import",
|
|
),
|
|
).toBe(true);
|
|
expect(
|
|
graph.nodes.flatMap(({ observations }) =>
|
|
observations.map(({ evidence }) => evidence.authority),
|
|
),
|
|
).toContain("managed-static-analysis");
|
|
|
|
const trace = traceApplicationFeatureEvidence({
|
|
application: parsed,
|
|
native_observations: [],
|
|
seed: {
|
|
kind: "string",
|
|
value: "MessageBoxW",
|
|
match: "exact",
|
|
case_sensitive: true,
|
|
},
|
|
direction: "incoming",
|
|
});
|
|
expect(trace.ok, trace.ok ? undefined : JSON.stringify(trace.error)).toBe(
|
|
true,
|
|
);
|
|
if (!trace.ok) throw new Error("trace failed");
|
|
expect(trace.value.normalized_result).toMatchObject({
|
|
source_evidence_id: parsed.evidence_id,
|
|
summary: { matched_seeds: 1 },
|
|
});
|
|
});
|
|
});
|
|
|
|
describe("managed application graph request", () => {
|
|
it("accepts any nonempty set of inline sources and rejects duplicates", () => {
|
|
const evidence = MANAGED_APPLICATION_GRAPH_EXAMPLE.managed_members;
|
|
expect(
|
|
managedApplicationGraphReferenceInputSchema.safeParse({
|
|
managed_members: evidence,
|
|
}).success,
|
|
).toBe(true);
|
|
expect(
|
|
managedApplicationGraphReferenceInputSchema.safeParse({}).success,
|
|
).toBe(false);
|
|
expect(
|
|
managedApplicationGraphReferenceInputSchema.safeParse({
|
|
managed_members: evidence,
|
|
managed_native_boundaries: evidence,
|
|
}).success,
|
|
).toBe(false);
|
|
});
|
|
});
|
|
|
|
describe("managed application graph coverage", () => {
|
|
it("preserves partial parser coverage in graph and per-fact coverage", () => {
|
|
const bytes = buildManagedPeFixture();
|
|
const binary = managedPeFixtureTarget(bytes, "/fixture/ManagedInterop.exe");
|
|
const members = inspectManagedMembersBytes(bytes, binary);
|
|
const parserPartialMembers = {
|
|
...members,
|
|
coverage: {
|
|
state: "partial" as const,
|
|
issues: [
|
|
{
|
|
code: "invalid-blob" as const,
|
|
scope: "metadata.#Blob",
|
|
offset: 0x0a00,
|
|
detail: "Blob content leaves #Blob",
|
|
},
|
|
],
|
|
},
|
|
};
|
|
const parserPartialEvidence = createEvidence(
|
|
binary,
|
|
MANAGED_STATIC_PROVIDER,
|
|
{
|
|
operation: "inspect_managed_members",
|
|
parameters: {},
|
|
result: parserPartialMembers,
|
|
rawResult: null,
|
|
limitations: parserPartialMembers.limitations,
|
|
},
|
|
);
|
|
const parserPartialProjection = projectManagedApplicationGraphEvidence({
|
|
managed_members: parserPartialEvidence,
|
|
});
|
|
|
|
expect(parserPartialProjection.ok).toBe(true);
|
|
if (!parserPartialProjection.ok)
|
|
throw new Error("partial projection failed");
|
|
const parserPartialResult = managedApplicationGraphResultSchema.parse(
|
|
parseEvidence(parserPartialProjection.value).normalized_result,
|
|
);
|
|
expect(parserPartialResult.coverage).toMatchObject({
|
|
status: "partial",
|
|
});
|
|
expect(parserPartialResult.graph.coverage).toEqual({
|
|
status: "partial",
|
|
truncated: false,
|
|
omitted_count: null,
|
|
limits: [],
|
|
});
|
|
});
|
|
});
|