fix(native): refuse inspect_signature on non-Mach-O targets (#2262)

* fix(native): refuse inspect_signature on non-Mach-O targets

This provider's signature inspection covers Mach-O code signatures only.
On a xar pkg, codesign's "code object is not signed" verdict cannot see
the archive-level signature layer, so a signed or invalidly-signed package
was reported as signed:false with "Artifact is not signed." Match the
existing inspect_macho/list_architectures gate and report
capability_unavailable instead of an unsigned claim for a layer that was
never evaluated.

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* test(native): scope the signature-gate comment to this provider

Generated with [Devin](https://devin.ai)

Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>

* fix(native): align signature capability discovery with Mach-O support

Bind inspect_signature availability to Mach-O targets in capability discovery and align the public tool description.

Source inspection and git diff --check; tests/builds/typechecks/CI reruns skipped at explicit user request.

---------

Co-authored-by: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: morluto <76467478+morluto@users.noreply.github.com>
This commit is contained in:
Mason 2026-10-13 21:22:42 +08:00 committed by GitHub
parent cae7620b77
commit 5eb296649d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 47 additions and 1 deletions

View File

@ -141,6 +141,7 @@ const FRIDA_OPERATION_NAMES: ReadonlySet<string> = new Set(
/** Native contracts defined only for an active Mach-O executable. */
const MACHO_TARGET_OPERATIONS: ReadonlySet<string> = new Set([
"inspect_signature",
"inspect_macho",
"list_architectures",
"observe_native_calls",

View File

@ -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(

View File

@ -564,6 +564,19 @@ class NativeMacOSClient implements AnalysisClient {
async #inspectSignature(
signal?: AbortSignal,
): Promise<Result<NativeObservation, AnalysisError>> {
// 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,

View File

@ -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"]) {}