diff --git a/src/application/CapabilityInventory.ts b/src/application/CapabilityInventory.ts index 4e364a1aa..f4584eb29 100644 --- a/src/application/CapabilityInventory.ts +++ b/src/application/CapabilityInventory.ts @@ -141,6 +141,7 @@ const FRIDA_OPERATION_NAMES: ReadonlySet = new Set( /** Native contracts defined only for an active Mach-O executable. */ const MACHO_TARGET_OPERATIONS: ReadonlySet = new Set([ + "inspect_signature", "inspect_macho", "list_architectures", "observe_native_calls", diff --git a/src/contracts/native/nativeToolContracts.ts b/src/contracts/native/nativeToolContracts.ts index 16101368b..d45b62cc6 100644 --- a/src/contracts/native/nativeToolContracts.ts +++ b/src/contracts/native/nativeToolContracts.ts @@ -107,7 +107,7 @@ export const NATIVE_TOOL_CONTRACTS = [ ), native( "inspect_signature", - "Inspect the active artifact's code-signing identity, hashes, authorities, requirements, entitlements, hardened-runtime state, and exact command provenance.", + "Inspect the active Mach-O executable's code-signing identity, hashes, authorities, requirements, entitlements, hardened-runtime state, and exact command provenance.", z.object({}), ), native( diff --git a/src/native/NativeMacOSProvider.ts b/src/native/NativeMacOSProvider.ts index 691e830d9..333569e47 100644 --- a/src/native/NativeMacOSProvider.ts +++ b/src/native/NativeMacOSProvider.ts @@ -564,6 +564,19 @@ class NativeMacOSClient implements AnalysisClient { async #inspectSignature( signal?: AbortSignal, ): Promise> { + // This provider's signature inspection covers Mach-O code signatures + // only. On another container, codesign's "not signed" verdict cannot + // see a format-level signature such as a xar pkg signature, so + // reporting it would claim unsigned for a layer that was never + // evaluated. + if (this.target.kind !== "executable" || !isMachOFormat(this.target.format)) + return err( + new AnalysisCapabilityUnavailableError( + IDENTITY.id, + "inspect_signature", + "Active artifact is not Mach-O; its container-level signature layer is not evaluated by codesign.", + ), + ); return withSignatureTarget( this.target, this.#signatureResources, diff --git a/tests/boundary/providers/native/nativeProvider.test.ts b/tests/boundary/providers/native/nativeProvider.test.ts index 81766e22d..a1e7d63ce 100644 --- a/tests/boundary/providers/native/nativeProvider.test.ts +++ b/tests/boundary/providers/native/nativeProvider.test.ts @@ -1024,6 +1024,38 @@ describe("native macOS provider failures and parsing", () => { }); }); +describe("native signature inspection target scope", () => { + // This provider's inspect_signature path is scoped to Mach-O targets; on + // another container a codesign verdict cannot see a format-level signature + // such as a xar pkg signature. + it.each([ + { + path: "/fixture.pkg", + sha256: "0".repeat(64), + kind: "archive" as const, + format: "pkg" as const, + }, + { + ...machoTarget("/fixture.elf"), + format: "elf" as const, + }, + ])( + "refuses inspect_signature on a $format target without running codesign", + async (target) => { + const runner = new CountingRunner(); + const result = await new NativeMacOSProvider({}, runner, "darwin") + .createClient(target) + .execute("inspect_signature", {}); + if (result.ok) throw new Error("Expected a non-Mach-O refusal"); + expect(result.error._tag).toBe("AnalysisCapabilityUnavailableError"); + expect(projectAnalysisError(result.error)).toMatchObject({ + code: "capability_unavailable", + }); + expect(runner.calls).toBe(0); + }, + ); +}); + class FailingRunner implements NativeCommandRunner { constructor(private readonly reason: NativeCommandFailure["reason"]) {}