fix(ghidra): omit duplicate load-image result (#7777)

* fix(ghidra): omit duplicate load-image result

* docs(mcp): describe load-image evidence projection
This commit is contained in:
Ha1baraA1 authored and GitHub committed 2026-10-29 08:23:38 +08:00
1 parent 64d282b0a6
commit a965cbab06
5 files changed
+59 -13

No files matched your search

+8 -7
View File
@@ -69,13 +69,14 @@ the same Evidence authentication failures: an admitted source that fails
authentication is `evidence_integrity_mismatch`, while an authenticated source with an unsupported
workflow role or invalid selector is `invalid_request`.
Ghidra inventories and function replies use `raw_result: null` when their facts
are already preserved in `normalized_result`, limitations, locations and
relationship coverage. Distinct overview details, measured load-image
observations, file-offset selection roles and database types supplemented with
derived metadata keep their upstream representation. Read function results from
`normalized_result`; older retained Evidence keeps its original representation
and identity.
Ghidra inventories, measured load-image observations and function replies use
`raw_result: null` when their useful facts are already preserved in
`normalized_result`, limitations, locations and relationship coverage. Distinct
overview details, file-offset selection roles and database types supplemented
with derived metadata keep their upstream representation. Native load-image
attestation continues to use the bridge reply locally before Evidence
projection. Read function results from `normalized_result`; older retained
Evidence keeps its original representation and identity.
Artifact source-map filename relationships require a unique, identified regular
file at the sibling path. A known directory cannot be the mapped file; the map
+11
View File
@@ -166,6 +166,17 @@ export type NativeLoadImage = z.infer<typeof nativeLoadImageSchema>;
export type NativeLoadImageObservation = z.infer<
typeof nativeLoadImageObservationSchema
>;
/** Remove entry-context rows that carry no register observations. */
export const normalizeNativeLoadImageObservation = (
observation: NativeLoadImageObservation,
): NativeLoadImageObservation => ({
...observation,
entry_context: observation.entry_context.filter(
({ registers }) => registers.length > 0,
),
});
/** One independently compared native import fact. */
export type NativeLoadImageCheck = z.infer<typeof nativeLoadImageCheckSchema>;
+35
View File
@@ -1,6 +1,7 @@
import { describe, expect, it } from "vitest";
import { z } from "zod";
import { nativeLoadImageObservationFixture } from "../../tests/fixtures/nativeLoadImage.js";
import {
parseGhidraInventoryInput,
parseGhidraInventoryResult,
@@ -98,6 +99,40 @@ it.each(["list_procedures", "search_procedures"] as const)(
},
);
describe("native load-image result projection", () => {
it("omits raw bridge output when the observation keeps every reported fact", () => {
const observations = JSON.parse(
JSON.stringify(nativeLoadImageObservationFixture()),
);
const parsed = parseGhidraInventoryResult(
"inspect_native_load_image",
observations,
);
expect(parsed.ok).toBe(true);
if (!parsed.ok) throw parsed.error;
expect(JSON.stringify(parsed.value.value)).toBe(
JSON.stringify(observations),
);
expect(parsed.value.rawResult).toBeUndefined();
});
it("omits entry-context rows without register observations", () => {
const observations = JSON.parse(
JSON.stringify({
...nativeLoadImageObservationFixture(),
entry_context: [{ address: "ram:0x0010", registers: [] }],
}),
);
const parsed = parseGhidraInventoryResult(
"inspect_native_load_image",
observations,
);
expect(parsed.ok).toBe(true);
if (!parsed.ok) throw parsed.error;
expect(JSON.stringify(parsed.value.value)).toContain('"entry_context":[]');
});
});
it("validates large masked byte queries at the provider boundary without changing caller spelling", () => {
const input = {
pattern: "aA??".repeat(1024 * 1024),
+2 -1
View File
@@ -11,6 +11,7 @@ import {
byteSearchResultSchema,
} from "../domain/native/bytePattern.js";
import {
normalizeNativeLoadImageObservation,
nativeLoadImageObservationSchema,
type NativeLoadImageObservation,
} from "../domain/native/nativeLoadImage.js";
@@ -365,7 +366,7 @@ export function parseGhidraInventoryResult(
if (operation === "inspect_native_load_image") {
const parsed = resultSchemas.inspect_native_load_image.safeParse(value);
return parsed.success
? ok({ value: parsed.data, rawResult: value })
? ok({ value: normalizeNativeLoadImageObservation(parsed.data) })
: err(inventoryOutputFailure(operation, parsed.error));
}
const parsed = resultSchemas[operation].safeParse(value);
+3 -5
View File
@@ -1811,7 +1811,7 @@ describe("Ghidra measured DOS load-image projection", () => {
ok: true,
value: {
result: { status: state, observations: fixture.observation },
rawResult: fixture.observation,
rawResult: null,
analysisProfile: profile.value.profile,
},
});
@@ -1894,7 +1894,7 @@ it("returns read-only NativeAOT metadata inline and drills down by derived Metho
],
},
},
rawResult: observations,
rawResult: null,
},
});
for (const address of [
@@ -2149,9 +2149,7 @@ it("shares one session snapshot across callers while isolating cancellation and
expect(secondResult).toMatchObject({
ok: true,
value: {
rawResult: {
source_files: [{ name: "caller-2" }],
},
rawResult: null,
result: {
observations: {
source_files: [{ name: "caller-2" }],