fix(managed): bind graph facts to their source Evidence
This commit is contained in:
parent
b112d44a39
commit
cc7b5af077
|
|
@ -151,8 +151,8 @@ export const projectManagedApplicationGraph = (
|
|||
};
|
||||
const artifactNode = addArtifactNode(state);
|
||||
addArtifactIdentityNodes(state, artifactNode, parsed);
|
||||
const memberCounts = addMemberNodes(state, artifactNode, parsed);
|
||||
const boundaryCounts = addBoundaryNodes(state, artifactNode, parsed);
|
||||
addMemberNodes(state, artifactNode, parsed);
|
||||
addBoundaryNodes(state, artifactNode, parsed);
|
||||
const omissions = omittedCounts(parsed);
|
||||
const limitations = projectionLimitations(parsed, omissions);
|
||||
const graph = createJavaScriptApplicationGraph({
|
||||
|
|
@ -175,10 +175,16 @@ export const projectManagedApplicationGraph = (
|
|||
summary: {
|
||||
graph_nodes: graph.nodes.length,
|
||||
graph_edges: graph.edges.length,
|
||||
assemblies: parsed.artifact?.result.assembly === null ? 0 : 1,
|
||||
modules: parsed.artifact?.result.module === null ? 0 : 1,
|
||||
...memberCounts,
|
||||
...boundaryCounts,
|
||||
assemblies: countNodes(graph.nodes, "managed-assembly"),
|
||||
modules: countNodes(graph.nodes, "managed-module"),
|
||||
types: countNodes(graph.nodes, "managed-type"),
|
||||
methods: countNodes(graph.nodes, "managed-method"),
|
||||
fields: countNodes(graph.nodes, "managed-field"),
|
||||
pinvoke_imports: countNodes(graph.nodes, "managed-pinvoke-import"),
|
||||
native_implementations: countNodes(
|
||||
graph.nodes,
|
||||
"managed-native-implementation",
|
||||
),
|
||||
},
|
||||
graph,
|
||||
coverage: managedGraphResultCoverage(omissions),
|
||||
|
|
@ -191,6 +197,11 @@ export const projectManagedApplicationGraph = (
|
|||
});
|
||||
};
|
||||
|
||||
const countNodes = (
|
||||
nodes: readonly ApplicationNode[],
|
||||
kind: ApplicationNode["kind"],
|
||||
): number => nodes.filter((node) => node.kind === kind).length;
|
||||
|
||||
const parseManagedInputs = (
|
||||
input: ProjectManagedApplicationGraphInput,
|
||||
): ParsedManagedGraphInput => ({
|
||||
|
|
|
|||
|
|
@ -1,11 +0,0 @@
|
|||
import type { ParsedManagedGraphInput } from "./managedApplicationGraph.js";
|
||||
|
||||
/** Derive module coverage from the best available managed evidence source. */
|
||||
export const moduleCoverageState = (
|
||||
parsed: ParsedManagedGraphInput,
|
||||
): "complete" | "partial" | "unavailable" =>
|
||||
parsed.artifact !== null && parsed.artifact.result.module !== null
|
||||
? parsed.artifact.result.coverage.state
|
||||
: parsed.members !== null && parsed.members.result.module !== null
|
||||
? parsed.members.result.coverage.state
|
||||
: (parsed.boundaries?.result.coverage.state ?? "complete");
|
||||
|
|
@ -6,7 +6,9 @@ import {
|
|||
import type { ApplicationGraphEvidence } from "../javascript/javascriptApplicationEvidenceSchemas.js";
|
||||
import type { ApplicationNode } from "../javascript/javascriptApplicationGraphSchemas.js";
|
||||
import { managedSourceCoverage } from "./managedApplicationGraphCoverage.js";
|
||||
import type { Evidence } from "../evidence.js";
|
||||
import {
|
||||
type ManagedArtifactInspection,
|
||||
type ManagedMemberInspection,
|
||||
type ManagedNativeBoundaryInspection,
|
||||
} from "./managedArtifact.js";
|
||||
|
|
@ -14,7 +16,6 @@ import type {
|
|||
GraphBuildState,
|
||||
ParsedManagedGraphInput,
|
||||
} from "./managedApplicationGraph.js";
|
||||
import { moduleCoverageState } from "./managedApplicationGraphNodeCoverage.js";
|
||||
type ManagedMethod = ManagedMemberInspection["methods"][number];
|
||||
type ManagedField = ManagedMemberInspection["fields"][number];
|
||||
type ManagedType = ManagedMemberInspection["types"][number];
|
||||
|
|
@ -44,7 +45,7 @@ export const addArtifactNode = (state: GraphBuildState): ApplicationNode => {
|
|||
format: "pe",
|
||||
source: "managed-application-graph",
|
||||
},
|
||||
evidence: managedEvidence(state, "project-managed-artifact"),
|
||||
evidence: projectedManagedEvidence(state, "project-managed-artifact"),
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -58,47 +59,48 @@ export const addArtifactIdentityNodes = (
|
|||
artifactNode: ApplicationNode,
|
||||
parsed: ParsedManagedGraphInput,
|
||||
): void => {
|
||||
const artifact = parsed.artifact?.result;
|
||||
const artifactCoverage = managedSourceCoverage(
|
||||
parsed.artifact?.result.coverage.state ?? "complete",
|
||||
);
|
||||
if (artifact?.assembly !== undefined && artifact.assembly !== null) {
|
||||
const artifactInput = parsed.artifact;
|
||||
if (artifactInput !== null && artifactInput.result.assembly !== null) {
|
||||
const assemblyFacts = artifactInput.result.assembly;
|
||||
const artifactEvidence = sourceManagedEvidence(
|
||||
state,
|
||||
artifactInput.evidence,
|
||||
artifactInput.result.coverage.state,
|
||||
);
|
||||
const assembly = createJavaScriptApplicationNode({
|
||||
kind: "managed-assembly",
|
||||
identity: artifactLocalIdentity(
|
||||
state.artifactSha256,
|
||||
"managed-assembly",
|
||||
artifact.assembly.token,
|
||||
assemblyFacts.token,
|
||||
),
|
||||
observations: [
|
||||
{
|
||||
label: artifact.assembly.name,
|
||||
label: assemblyFacts.name,
|
||||
properties: {
|
||||
name: artifact.assembly.name,
|
||||
version: artifact.assembly.version,
|
||||
culture: artifact.assembly.culture,
|
||||
public_key_kind: artifact.assembly.public_key.kind,
|
||||
name: assemblyFacts.name,
|
||||
version: assemblyFacts.version,
|
||||
culture: assemblyFacts.culture,
|
||||
public_key_kind: assemblyFacts.public_key.kind,
|
||||
},
|
||||
evidence: managedEvidence(
|
||||
state,
|
||||
"inspect_managed_artifact",
|
||||
artifactCoverage,
|
||||
),
|
||||
evidence: artifactEvidence,
|
||||
},
|
||||
],
|
||||
});
|
||||
state.nodes.push(assembly);
|
||||
addContainsEdge(state, artifactNode, assembly, {
|
||||
kind: "declares-managed-assembly",
|
||||
coverage: artifactCoverage,
|
||||
coverage: artifactEvidence.coverage,
|
||||
});
|
||||
}
|
||||
const module =
|
||||
artifact?.module ??
|
||||
parsed.members?.result.module ??
|
||||
parsed.boundaries?.result.module;
|
||||
if (module !== undefined && module !== null) {
|
||||
const moduleCoverage = managedSourceCoverage(moduleCoverageState(parsed));
|
||||
const moduleSource = selectModuleSource(parsed);
|
||||
if (moduleSource !== null) {
|
||||
const { module } = moduleSource;
|
||||
const moduleEvidence = sourceManagedEvidence(
|
||||
state,
|
||||
moduleSource.evidence,
|
||||
moduleSource.coverageState,
|
||||
);
|
||||
const moduleNode = createJavaScriptApplicationNode({
|
||||
kind: "managed-module",
|
||||
identity: artifactLocalIdentity(
|
||||
|
|
@ -115,36 +117,68 @@ export const addArtifactIdentityNodes = (
|
|||
generation: module.generation,
|
||||
token: module.token,
|
||||
},
|
||||
evidence: managedEvidence(
|
||||
state,
|
||||
"inspect_managed_artifact",
|
||||
moduleCoverage,
|
||||
),
|
||||
evidence: moduleEvidence,
|
||||
},
|
||||
],
|
||||
});
|
||||
state.nodes.push(moduleNode);
|
||||
addContainsEdge(state, artifactNode, moduleNode, {
|
||||
kind: "declares-managed-module",
|
||||
coverage: moduleCoverage,
|
||||
coverage: moduleEvidence.coverage,
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
interface ManagedModuleSource {
|
||||
readonly module: NonNullable<ManagedArtifactInspection["module"]>;
|
||||
readonly evidence: Evidence;
|
||||
readonly coverageState: ManagedArtifactInspection["coverage"]["state"];
|
||||
}
|
||||
|
||||
const selectModuleSource = (
|
||||
parsed: ParsedManagedGraphInput,
|
||||
): ManagedModuleSource | null => {
|
||||
if (parsed.artifact?.result.module != null)
|
||||
return {
|
||||
module: parsed.artifact.result.module,
|
||||
evidence: parsed.artifact.evidence,
|
||||
coverageState: parsed.artifact.result.coverage.state,
|
||||
};
|
||||
if (parsed.members?.result.module != null)
|
||||
return {
|
||||
module: parsed.members.result.module,
|
||||
evidence: parsed.members.evidence,
|
||||
coverageState: parsed.members.result.coverage.state,
|
||||
};
|
||||
if (parsed.boundaries?.result.module != null)
|
||||
return {
|
||||
module: parsed.boundaries.result.module,
|
||||
evidence: parsed.boundaries.evidence,
|
||||
coverageState: parsed.boundaries.result.coverage.state,
|
||||
};
|
||||
return null;
|
||||
};
|
||||
|
||||
/** Add managed type, method, and field nodes from member evidence. */
|
||||
export const addMemberNodes = (
|
||||
state: GraphBuildState,
|
||||
artifactNode: ApplicationNode,
|
||||
parsed: ParsedManagedGraphInput,
|
||||
) => {
|
||||
const members = parsed.members?.result;
|
||||
if (members === undefined) return { types: 0, methods: 0, fields: 0 };
|
||||
): void => {
|
||||
const memberInput = parsed.members;
|
||||
if (memberInput === null) return;
|
||||
const members = memberInput.result;
|
||||
const { types, methods, fields } = members;
|
||||
const typeCoverage = managedSourceCoverage(members.coverage.state);
|
||||
const methodCoverage = managedSourceCoverage(members.coverage.state);
|
||||
const fieldCoverage = managedSourceCoverage(members.coverage.state);
|
||||
const sourceEvidence = sourceManagedEvidence(
|
||||
state,
|
||||
memberInput.evidence,
|
||||
members.coverage.state,
|
||||
);
|
||||
for (const type of types) {
|
||||
const node = typeNode(state, type, typeCoverage);
|
||||
const node = typeNode(state, type, sourceEvidence);
|
||||
state.nodes.push(node);
|
||||
state.typeNodes.set(type.token, node);
|
||||
addContainsEdge(state, artifactNode, node, {
|
||||
|
|
@ -153,7 +187,7 @@ export const addMemberNodes = (
|
|||
});
|
||||
}
|
||||
for (const method of methods) {
|
||||
const node = methodNode(state, method, methodCoverage);
|
||||
const node = methodNode(state, method, sourceEvidence);
|
||||
state.nodes.push(node);
|
||||
state.methodNodes.set(method.token, node);
|
||||
const owner =
|
||||
|
|
@ -166,7 +200,7 @@ export const addMemberNodes = (
|
|||
});
|
||||
}
|
||||
for (const field of fields) {
|
||||
const node = fieldNode(state, field, fieldCoverage);
|
||||
const node = fieldNode(state, field, sourceEvidence);
|
||||
state.nodes.push(node);
|
||||
state.fieldNodes.set(field.token, node);
|
||||
const owner =
|
||||
|
|
@ -178,18 +212,13 @@ export const addMemberNodes = (
|
|||
coverage: fieldCoverage,
|
||||
});
|
||||
}
|
||||
return {
|
||||
types: types.length,
|
||||
methods: methods.length,
|
||||
fields: fields.length,
|
||||
};
|
||||
};
|
||||
|
||||
/** Build a managed type application node. */
|
||||
const typeNode = (
|
||||
state: GraphBuildState,
|
||||
type: ManagedType,
|
||||
coverage: ApplicationGraphEvidence["coverage"],
|
||||
evidence: ApplicationGraphEvidence,
|
||||
): ApplicationNode =>
|
||||
createJavaScriptApplicationNode({
|
||||
kind: "managed-type",
|
||||
|
|
@ -209,7 +238,7 @@ const typeNode = (
|
|||
flags: type.flags,
|
||||
extends_token: type.extends_token,
|
||||
},
|
||||
evidence: managedEvidence(state, "inspect_managed_members", coverage),
|
||||
evidence,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -218,7 +247,7 @@ const typeNode = (
|
|||
const methodNode = (
|
||||
state: GraphBuildState,
|
||||
method: ManagedMethod,
|
||||
coverage: ApplicationGraphEvidence["coverage"],
|
||||
evidence: ApplicationGraphEvidence,
|
||||
): ApplicationNode =>
|
||||
createJavaScriptApplicationNode({
|
||||
kind: "managed-method",
|
||||
|
|
@ -245,7 +274,7 @@ const methodNode = (
|
|||
normalized_il_sha256: method.body.normalized_il_sha256,
|
||||
il_size: method.body.il_size,
|
||||
},
|
||||
evidence: managedEvidence(state, "inspect_managed_members", coverage),
|
||||
evidence,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -254,7 +283,7 @@ const methodNode = (
|
|||
const fieldNode = (
|
||||
state: GraphBuildState,
|
||||
field: ManagedField,
|
||||
coverage: ApplicationGraphEvidence["coverage"],
|
||||
evidence: ApplicationGraphEvidence,
|
||||
): ApplicationNode =>
|
||||
createJavaScriptApplicationNode({
|
||||
kind: "managed-field",
|
||||
|
|
@ -278,7 +307,7 @@ const fieldNode = (
|
|||
signature_sha256: field.signature.raw_sha256,
|
||||
signature_status: field.signature.parse_status,
|
||||
},
|
||||
evidence: managedEvidence(state, "inspect_managed_members", coverage),
|
||||
evidence,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -288,23 +317,33 @@ export const addBoundaryNodes = (
|
|||
state: GraphBuildState,
|
||||
artifactNode: ApplicationNode,
|
||||
parsed: ParsedManagedGraphInput,
|
||||
) => {
|
||||
const boundaries = parsed.boundaries?.result;
|
||||
if (boundaries === undefined)
|
||||
return { pinvoke_imports: 0, native_implementations: 0 };
|
||||
): void => {
|
||||
const boundaryInput = parsed.boundaries;
|
||||
if (boundaryInput === null) return;
|
||||
const boundaries = boundaryInput.result;
|
||||
const { pinvoke_imports: pinvokes, native_implementations: implementations } =
|
||||
boundaries;
|
||||
const pinvokeCoverage = managedSourceCoverage(boundaries.coverage.state);
|
||||
const implementationCoverage = managedSourceCoverage(
|
||||
boundaries.coverage.state,
|
||||
);
|
||||
const sourceEvidence = sourceManagedEvidence(
|
||||
state,
|
||||
boundaryInput.evidence,
|
||||
boundaries.coverage.state,
|
||||
);
|
||||
for (const pinvoke of pinvokes) {
|
||||
const node = pinvokeNode(state, pinvoke, pinvokeCoverage);
|
||||
const node = pinvokeNode(state, pinvoke, sourceEvidence);
|
||||
state.nodes.push(node);
|
||||
const owner =
|
||||
const method =
|
||||
pinvoke.member_token === null
|
||||
? artifactNode
|
||||
: (state.methodNodes.get(pinvoke.member_token) ?? artifactNode);
|
||||
? undefined
|
||||
: state.methodNodes.get(pinvoke.member_token);
|
||||
const memberEvidence =
|
||||
method === undefined || parsed.members === null
|
||||
? undefined
|
||||
: parsed.members.evidence;
|
||||
const owner = method ?? artifactNode;
|
||||
state.edges.push(
|
||||
createJavaScriptApplicationEdge({
|
||||
source_node_id: owner.node_id,
|
||||
|
|
@ -315,10 +354,11 @@ export const addBoundaryNodes = (
|
|||
import_name: pinvoke.import_name,
|
||||
import_scope_name: pinvoke.import_scope_name,
|
||||
},
|
||||
evidence: managedEvidence(
|
||||
evidence: pinvokeAssociationEvidence(
|
||||
state,
|
||||
"inspect_managed_native_boundaries",
|
||||
boundaryInput.evidence,
|
||||
pinvokeCoverage,
|
||||
memberEvidence,
|
||||
),
|
||||
}),
|
||||
);
|
||||
|
|
@ -327,7 +367,7 @@ export const addBoundaryNodes = (
|
|||
const node = nativeImplementationNode(
|
||||
state,
|
||||
implementation,
|
||||
implementationCoverage,
|
||||
sourceEvidence,
|
||||
);
|
||||
state.nodes.push(node);
|
||||
const owner = state.methodNodes.get(implementation.token) ?? artifactNode;
|
||||
|
|
@ -336,17 +376,13 @@ export const addBoundaryNodes = (
|
|||
coverage: implementationCoverage,
|
||||
});
|
||||
}
|
||||
return {
|
||||
pinvoke_imports: pinvokes.length,
|
||||
native_implementations: implementations.length,
|
||||
};
|
||||
};
|
||||
|
||||
/** Build a managed P/Invoke import application node. */
|
||||
const pinvokeNode = (
|
||||
state: GraphBuildState,
|
||||
pinvoke: PinvokeImport,
|
||||
coverage: ApplicationGraphEvidence["coverage"],
|
||||
evidence: ApplicationGraphEvidence,
|
||||
): ApplicationNode =>
|
||||
createJavaScriptApplicationNode({
|
||||
kind: "managed-pinvoke-import",
|
||||
|
|
@ -368,11 +404,7 @@ const pinvokeNode = (
|
|||
call_convention: pinvoke.call_convention,
|
||||
verification: pinvoke.verification,
|
||||
},
|
||||
evidence: managedEvidence(
|
||||
state,
|
||||
"inspect_managed_native_boundaries",
|
||||
coverage,
|
||||
),
|
||||
evidence,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -381,7 +413,7 @@ const pinvokeNode = (
|
|||
const nativeImplementationNode = (
|
||||
state: GraphBuildState,
|
||||
implementation: NativeImplementation,
|
||||
coverage: ApplicationGraphEvidence["coverage"],
|
||||
evidence: ApplicationGraphEvidence,
|
||||
): ApplicationNode =>
|
||||
createJavaScriptApplicationNode({
|
||||
kind: "managed-native-implementation",
|
||||
|
|
@ -403,11 +435,7 @@ const nativeImplementationNode = (
|
|||
boundary_kind: implementation.boundary_kind,
|
||||
body_interpretation: implementation.body_interpretation,
|
||||
},
|
||||
evidence: managedEvidence(
|
||||
state,
|
||||
"inspect_managed_native_boundaries",
|
||||
coverage,
|
||||
),
|
||||
evidence,
|
||||
},
|
||||
],
|
||||
});
|
||||
|
|
@ -425,7 +453,7 @@ const addContainsEdge = (
|
|||
target_node_id: target.node_id,
|
||||
relation: "contains",
|
||||
properties: { kind: options.kind },
|
||||
evidence: managedEvidence(
|
||||
evidence: projectedManagedEvidence(
|
||||
state,
|
||||
"project-managed-contains",
|
||||
options.coverage,
|
||||
|
|
@ -448,12 +476,43 @@ const artifactLocalIdentity = (
|
|||
});
|
||||
|
||||
/** Build managed static-analysis evidence for a graph observation. */
|
||||
const managedEvidence = (
|
||||
const sourceManagedEvidence = (
|
||||
state: GraphBuildState,
|
||||
source: Evidence,
|
||||
coverageState: ManagedArtifactInspection["coverage"]["state"],
|
||||
): ApplicationGraphEvidence =>
|
||||
createManagedEvidence(
|
||||
state,
|
||||
source.operation,
|
||||
managedSourceCoverage(coverageState),
|
||||
[source.evidence_id],
|
||||
);
|
||||
|
||||
const projectedManagedEvidence = (
|
||||
state: GraphBuildState,
|
||||
operation: string,
|
||||
coverage: ApplicationGraphEvidence["coverage"] = managedSourceCoverage(
|
||||
"complete",
|
||||
),
|
||||
): ApplicationGraphEvidence =>
|
||||
createManagedEvidence(state, operation, coverage, state.evidenceLinks);
|
||||
|
||||
const pinvokeAssociationEvidence = (
|
||||
state: GraphBuildState,
|
||||
boundaryEvidence: Evidence,
|
||||
coverage: ApplicationGraphEvidence["coverage"],
|
||||
memberEvidence: Evidence | undefined,
|
||||
): ApplicationGraphEvidence =>
|
||||
createManagedEvidence(state, boundaryEvidence.operation, coverage, [
|
||||
boundaryEvidence.evidence_id,
|
||||
...(memberEvidence === undefined ? [] : [memberEvidence.evidence_id]),
|
||||
]);
|
||||
|
||||
const createManagedEvidence = (
|
||||
state: GraphBuildState,
|
||||
operation: string,
|
||||
coverage: ApplicationGraphEvidence["coverage"],
|
||||
evidenceIds: readonly string[],
|
||||
): ApplicationGraphEvidence => ({
|
||||
authority: "managed-static-analysis",
|
||||
state: "observed",
|
||||
|
|
@ -483,7 +542,7 @@ const managedEvidence = (
|
|||
: [
|
||||
"The source managed Evidence slice is incomplete; unreturned or unavailable items remain unobserved.",
|
||||
],
|
||||
evidence_ids: state.evidenceLinks,
|
||||
evidence_ids: [...evidenceIds],
|
||||
});
|
||||
|
||||
/** Sanitize an artifact path into a managed graph location. */
|
||||
|
|
|
|||
|
|
@ -16,43 +16,51 @@ import {
|
|||
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, {
|
||||
const createManagedInteropEvidence = () => {
|
||||
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);
|
||||
|
||||
return {
|
||||
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, {
|
||||
}),
|
||||
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, {
|
||||
}),
|
||||
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 }],
|
||||
});
|
||||
}),
|
||||
};
|
||||
};
|
||||
|
||||
describe("managed application graph projection", () => {
|
||||
it("projects managed metadata and native declarations into authenticated graph Evidence", () => {
|
||||
const { artifactEvidence, memberEvidence, boundaryEvidence } =
|
||||
createManagedInteropEvidence();
|
||||
|
||||
const evidence = projectManagedApplicationGraphEvidence({
|
||||
managed_artifact: artifactEvidence,
|
||||
|
|
@ -103,21 +111,51 @@ describe("managed application graph projection", () => {
|
|||
"managed-pinvoke-import",
|
||||
]),
|
||||
);
|
||||
const allEvidenceIds = [
|
||||
artifactEvidence.evidence_id,
|
||||
memberEvidence.evidence_id,
|
||||
boundaryEvidence.evidence_id,
|
||||
].sort();
|
||||
const observedEvidenceIds = (kind: string): readonly string[] => {
|
||||
const observation = graph.nodes.find((node) => node.kind === kind)
|
||||
?.observations[0];
|
||||
if (observation === undefined)
|
||||
throw new Error(`Expected ${kind} observation`);
|
||||
return observation.evidence.evidence_ids;
|
||||
};
|
||||
expect(observedEvidenceIds("artifact")).toEqual(allEvidenceIds);
|
||||
expect(observedEvidenceIds("managed-assembly")).toEqual([
|
||||
artifactEvidence.evidence_id,
|
||||
]);
|
||||
expect(observedEvidenceIds("managed-module")).toEqual([
|
||||
artifactEvidence.evidence_id,
|
||||
]);
|
||||
for (const kind of ["managed-type", "managed-method", "managed-field"])
|
||||
expect(observedEvidenceIds(kind)).toEqual([memberEvidence.evidence_id]);
|
||||
expect(observedEvidenceIds("managed-pinvoke-import")).toEqual([
|
||||
boundaryEvidence.evidence_id,
|
||||
]);
|
||||
for (const edge of graph.edges.filter(
|
||||
({ relation }) => relation === "contains",
|
||||
))
|
||||
expect(edge.evidence.evidence_ids).toEqual(allEvidenceIds);
|
||||
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);
|
||||
const importEdge = graph.edges.find(
|
||||
({ 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",
|
||||
);
|
||||
expect(importEdge).toBeDefined();
|
||||
expect(importEdge?.evidence.evidence_ids).toEqual(
|
||||
[memberEvidence.evidence_id, boundaryEvidence.evidence_id].sort(),
|
||||
);
|
||||
expect(
|
||||
graph.nodes.flatMap(({ observations }) =>
|
||||
observations.map(({ evidence }) => evidence.authority),
|
||||
|
|
@ -167,7 +205,7 @@ describe("managed application graph request", () => {
|
|||
});
|
||||
|
||||
describe("managed application graph coverage", () => {
|
||||
it("rejects managed Evidence digest mismatch and discloses missing artifact observations", () => {
|
||||
it("rejects managed Evidence whose artifact digest differs from its subject", () => {
|
||||
const bytes = buildManagedPeFixture();
|
||||
const binary = managedPeFixtureTarget(bytes, "/fixture/Mismatch.dll");
|
||||
const members = inspectManagedMembersBytes(bytes, binary);
|
||||
|
|
@ -191,7 +229,14 @@ describe("managed application graph coverage", () => {
|
|||
expect(JSON.stringify(rejected.error)).toContain(
|
||||
`Managed Evidence inspect_managed_members (${mismatched.evidence_id}) subject SHA-256 ${binary.sha256} does not match normalized artifact SHA-256 ${"b".repeat(64)}`,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("managed application graph identity facts", () => {
|
||||
it("uses member Evidence for its module and reports only emitted identity nodes", () => {
|
||||
const bytes = buildManagedPeFixture();
|
||||
const binary = managedPeFixtureTarget(bytes, "/fixture/MembersOnly.dll");
|
||||
const members = inspectManagedMembersBytes(bytes, binary);
|
||||
const aligned = createEvidence(binary, MANAGED_STATIC_PROVIDER, {
|
||||
operation: "inspect_managed_members",
|
||||
parameters: {},
|
||||
|
|
@ -204,11 +249,92 @@ describe("managed application graph coverage", () => {
|
|||
const result = managedApplicationGraphResultSchema.parse(
|
||||
parseEvidence(projected.value).normalized_result,
|
||||
);
|
||||
const graph = parseJavaScriptApplicationGraph(result.graph);
|
||||
expect(result.summary).toMatchObject({ assemblies: 0, modules: 1 });
|
||||
expect(
|
||||
graph.nodes.find(({ kind }) => kind === "managed-module")
|
||||
?.observations[0],
|
||||
).toMatchObject({
|
||||
evidence: {
|
||||
extractor: { operation: "inspect_managed_members" },
|
||||
evidence_ids: [aligned.evidence_id],
|
||||
},
|
||||
});
|
||||
expect(result.evidence_links).toEqual([aligned.evidence_id]);
|
||||
expect(result.limitations).toContain(
|
||||
"Managed artifact Evidence was not supplied; assembly identity observations are absent.",
|
||||
);
|
||||
});
|
||||
|
||||
it("binds a boundaries-only module node to the boundary Evidence that supplied it", () => {
|
||||
const bytes = buildManagedPeFixture();
|
||||
const binary = managedPeFixtureTarget(bytes, "/fixture/BoundariesOnly.dll");
|
||||
const boundaries = inspectManagedNativeBoundariesBytes(bytes, binary);
|
||||
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 projected = projectManagedApplicationGraphEvidence({
|
||||
managed_native_boundaries: boundaryEvidence,
|
||||
});
|
||||
if (!projected.ok) throw projected.error;
|
||||
const result = managedApplicationGraphResultSchema.parse(
|
||||
parseEvidence(projected.value).normalized_result,
|
||||
);
|
||||
const graph = parseJavaScriptApplicationGraph(result.graph);
|
||||
expect(result.summary).toMatchObject({ assemblies: 0, modules: 1 });
|
||||
expect(
|
||||
graph.nodes.find(({ kind }) => kind === "managed-module")
|
||||
?.observations[0],
|
||||
).toMatchObject({
|
||||
evidence: {
|
||||
extractor: { operation: "inspect_managed_native_boundaries" },
|
||||
evidence_ids: [boundaryEvidence.evidence_id],
|
||||
},
|
||||
});
|
||||
expect(result.evidence_links).toEqual([boundaryEvidence.evidence_id]);
|
||||
});
|
||||
|
||||
it("reports no assembly or module when the inspector observes no identity rows", () => {
|
||||
const bytes = buildManagedPeFixture({
|
||||
moduleRowCount: 0,
|
||||
assemblyRowCount: 0,
|
||||
});
|
||||
const binary = managedPeFixtureTarget(bytes, "/fixture/NoIdentityRows.exe");
|
||||
const inspection = inspectManagedArtifactBytes(bytes, binary);
|
||||
expect(inspection).toMatchObject({ module: null, assembly: null });
|
||||
const artifactEvidence = createEvidence(binary, MANAGED_STATIC_PROVIDER, {
|
||||
operation: "inspect_managed_artifact",
|
||||
parameters: {},
|
||||
result: inspection,
|
||||
rawResult: null,
|
||||
limitations: inspection.limitations,
|
||||
locations: [{ kind: "artifact-path", path: binary.path }],
|
||||
});
|
||||
|
||||
const projected = projectManagedApplicationGraphEvidence({
|
||||
managed_artifact: artifactEvidence,
|
||||
});
|
||||
if (!projected.ok) throw projected.error;
|
||||
const result = managedApplicationGraphResultSchema.parse(
|
||||
parseEvidence(projected.value).normalized_result,
|
||||
);
|
||||
const graph = parseJavaScriptApplicationGraph(result.graph);
|
||||
expect(result.summary).toMatchObject({ assemblies: 0, modules: 0 });
|
||||
expect(
|
||||
graph.nodes.filter(
|
||||
({ kind }) => kind === "managed-assembly" || kind === "managed-module",
|
||||
),
|
||||
).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
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");
|
||||
|
|
|
|||
Loading…
Reference in New Issue