diff --git a/src/domain/managed/managedArtifact.ts b/src/domain/managed/managedArtifact.ts index 5b0d5a2ed9..a6734c2c4f 100644 --- a/src/domain/managed/managedArtifact.ts +++ b/src/domain/managed/managedArtifact.ts @@ -277,7 +277,7 @@ const managedModuleReferenceSchema = z.object({ name: z.string(), }); -const managedNativeImportSchema = z.object({ +export const managedNativeImportSchema = z.object({ token: managedMetadataTokenSchema, row_offset: offsetSchema, mapping_flags: z.number().int().min(0).max(0xffff), diff --git a/src/domain/managed/managedNativeVerificationSchemas.ts b/src/domain/managed/managedNativeVerificationSchemas.ts index d85016a585..8e88e113c4 100644 --- a/src/domain/managed/managedNativeVerificationSchemas.ts +++ b/src/domain/managed/managedNativeVerificationSchemas.ts @@ -4,7 +4,7 @@ import { emptyArraySchema } from "../emptyArraySchema.js"; import { evidenceSchema } from "../evidence.js"; import { cliMetadataGuidSchema, - managedMetadataTokenSchema, + managedNativeImportSchema, } from "./managedArtifact.js"; import { digestSchema } from "../digests.js"; import { prefixedDigestSchema } from "../digests.js"; @@ -24,27 +24,26 @@ const nativeSymbolSchema = z.strictObject({ export type NativeSymbol = z.infer; +const verifiedManagedDeclarationSchema = managedNativeImportSchema + .pick({ + token: true, + member_token: true, + member_name: true, + import_name: true, + import_scope_name: true, + no_mangle: true, + char_set: true, + call_convention: true, + }) + .extend({ + import_name: z.string().min(1), + declaration_verification: managedNativeImportSchema.shape.verification, + }) + .strict(); + const pinvokeVerificationContextShape = { item_id: prefixedDigestSchema("mnv_pinvoke"), - managed: z.strictObject({ - token: managedMetadataTokenSchema, - member_token: managedMetadataTokenSchema.nullable(), - member_name: z.string().nullable(), - import_name: z.string().min(1), - import_scope_name: z.string().nullable(), - no_mangle: z.boolean(), - char_set: z.enum(["not-specified", "ansi", "unicode", "auto", "unknown"]), - call_convention: z.enum([ - "not-specified", - "winapi", - "cdecl", - "stdcall", - "thiscall", - "fastcall", - "unknown", - ]), - declaration_verification: z.literal("managed-declaration-only"), - }), + managed: verifiedManagedDeclarationSchema, evidence_links: z.array(evidenceIdSchema).min(1), limitations: z.array(textSchema), }; diff --git a/tests/conformance/providers/managed/managedNativeVerification.test.ts b/tests/conformance/providers/managed/managedNativeVerification.test.ts index bd39d4b277..6f5bcb0603 100644 --- a/tests/conformance/providers/managed/managedNativeVerification.test.ts +++ b/tests/conformance/providers/managed/managedNativeVerification.test.ts @@ -6,6 +6,7 @@ import { createEvidence } from "../../../../src/domain/evidence.js"; import { managedNativeVerificationInputSchema, managedNativeVerificationResultSchema, + pinvokeVerificationSchema, } from "../../../../src/domain/managed/managedNativeVerificationSchemas.js"; import { managedNativeBoundaryInspectionSchema } from "../../../../src/domain/managed/managedArtifact.js"; import { inspectMachoSchema } from "../../../../src/domain/native/nativeInspection.js"; @@ -89,6 +90,31 @@ const inputWithImportScope = (scope: string | null) => { }; }; +it("keeps empty import names readable in managed Evidence but invalid in verification output", () => { + const input = exampleInput(); + const parsed = managedNativeBoundaryInspectionSchema.parse( + input.managed_boundaries.normalized_result, + ); + const declaration = parsed.pinvoke_imports[0]; + if (declaration === undefined) throw new Error("missing P/Invoke example"); + + const historicalBoundary = managedNativeBoundaryInspectionSchema.parse({ + ...parsed, + pinvoke_imports: [{ ...declaration, import_name: "" }], + }); + expect(historicalBoundary.pinvoke_imports[0]?.import_name).toBe(""); + + const verification = verifyManagedNativeBoundaries(input).pinvoke_imports[0]; + if (verification === undefined) + throw new Error("missing verification result"); + expect( + pinvokeVerificationSchema.safeParse({ + ...verification, + managed: { ...verification.managed, import_name: "" }, + }).success, + ).toBe(false); +}); + describe("managed/native boundary Evidence identity", () => { it("rejects managed boundary Evidence whose subject digest differs from its artifact", () => { const input = exampleInput();