feat(native): return a retained summary view of binary layouts on request (#1570)
inspect_binary_layout accepts the same optional detail input as analyze_javascript_application. The default complete delivery is unchanged; detail "summary" retains the complete layout Evidence in the session and returns its inspect_analysis_view summary, whose parent_evidence_id selects sections, symbols, mitigations or linkage later. Both tools share one detail schema and output projection. Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
c19e15a2b3
commit
5156ba44c5
|
|
@ -150,9 +150,10 @@ list stays complete; availability depends on the operation, target, and host.
|
|||
## Work summary-first
|
||||
|
||||
Start with the default result and use its inline Evidence and graph context.
|
||||
For a JavaScript application whose size is unknown or large, call
|
||||
`analyze_javascript_application` with `"detail": "summary"`, then inspect the
|
||||
retained `parent_evidence_id` with `inspect_analysis_view`.
|
||||
For a JavaScript application or ELF layout whose size is unknown or large,
|
||||
call `analyze_javascript_application` or `inspect_binary_layout` with
|
||||
`"detail": "summary"`, then inspect the retained `parent_evidence_id` with
|
||||
`inspect_analysis_view`.
|
||||
Do not repeat an identical tool call. Make a focused follow-up only when the
|
||||
returned result leaves a specific question unanswered.
|
||||
|
||||
|
|
|
|||
|
|
@ -91,6 +91,12 @@ one-shot invocations pass portable inline Evidence JSON; an MCP session uses
|
|||
the retained `evidence_id`. Page `limit` is required and bounded by the
|
||||
measured MCP stdio budget documented on the tool contract.
|
||||
|
||||
Over MCP, `inspect_binary_layout` with `"detail": "summary"` retains the
|
||||
complete layout Evidence in the session and returns only its summary view.
|
||||
Its `normalized_result.parent_evidence_id` names the retained layout for later
|
||||
section, symbol, mitigation or linkage views. The default `complete` detail and
|
||||
CLI output are unchanged.
|
||||
|
||||
Complete results have a 32 MiB input, 64 MiB reply, 1 MiB combined diagnostics
|
||||
and 30-second owned command deadline. The reply budget applies to the decoder
|
||||
record; the CLI/MCP Evidence envelope adds copies and encoding overhead. The
|
||||
|
|
|
|||
|
|
@ -318,12 +318,14 @@ Ordinary responses keep their existing complete result contract.
|
|||
|
||||
## Retained application Evidence inputs
|
||||
|
||||
`analyze_javascript_application` accepts `detail`: `complete` (default) returns
|
||||
the complete analysis Evidence; `summary` records that complete Evidence in the
|
||||
session and returns the derived `inspect_analysis_view` summary Evidence
|
||||
instead. Its `normalized_result.parent_evidence_id` and `evidence_links` name
|
||||
the retained analysis, so later views and application workflows read it without
|
||||
repeating analysis or moving the complete graph through the response.
|
||||
`analyze_javascript_application` and `inspect_binary_layout` accept `detail`:
|
||||
`complete` (default) returns the complete analysis Evidence; `summary` records
|
||||
that complete Evidence in the session and returns the derived
|
||||
`inspect_analysis_view` summary Evidence instead. Its
|
||||
`normalized_result.parent_evidence_id` and `evidence_links` name the retained
|
||||
analysis, so later views and application workflows read it without repeating
|
||||
analysis or moving the complete record through the response. A server without
|
||||
session retention refuses summary detail with `capability_unavailable`.
|
||||
|
||||
`inspect_analysis_view` projects a caller-selected view of already completed
|
||||
`inspect_binary_layout`, `analyze_javascript_application`, or `analyze_function`
|
||||
|
|
|
|||
|
|
@ -0,0 +1,17 @@
|
|||
import { z } from "zod";
|
||||
|
||||
import { analysisViewResultSchema } from "../domain/analysisView/analysisView.js";
|
||||
import { evidenceResultOf } from "./toolOutputSchemaPrimitives.js";
|
||||
|
||||
/** Caller-selected delivery of completed analysis Evidence that supports retained views. */
|
||||
export const analysisDetailSchema = z
|
||||
.enum(["complete", "summary"])
|
||||
.default("complete")
|
||||
.describe(
|
||||
"complete returns the complete analysis Evidence inline. summary retains that complete Evidence in this session and returns its inspect_analysis_view summary; inspect pages, items or facets through normalized_result.parent_evidence_id.",
|
||||
);
|
||||
|
||||
/** Complete analysis Evidence, or the summary view of its retained record. */
|
||||
export const analysisDetailOutputSchemaOf = <Schema extends z.ZodType>(
|
||||
analysis: Schema,
|
||||
) => evidenceResultOf(z.union([analysis, analysisViewResultSchema]));
|
||||
|
|
@ -11,9 +11,10 @@ import {
|
|||
electronActiveObservationInputSchema,
|
||||
electronActiveObservationResultSchema,
|
||||
} from "../../domain/javascript/electronActiveObservation.js";
|
||||
import { z } from "zod";
|
||||
|
||||
import { analysisViewResultSchema } from "../../domain/analysisView/analysisView.js";
|
||||
import {
|
||||
analysisDetailOutputSchemaOf,
|
||||
analysisDetailSchema,
|
||||
} from "../analysisDetail.js";
|
||||
import {
|
||||
analyzeJavaScriptApplicationInputSchema,
|
||||
javascriptApplicationAnalysisResultSchema,
|
||||
|
|
@ -29,18 +30,10 @@ const inspectionOutputSchema = evidenceResultOf(electronPageInspectionSchema);
|
|||
/** Caller-selected delivery of a completed JavaScript application analysis. */
|
||||
export const analyzeJavaScriptApplicationRequestSchema =
|
||||
analyzeJavaScriptApplicationInputSchema.extend({
|
||||
detail: z
|
||||
.enum(["complete", "summary"])
|
||||
.default("complete")
|
||||
.describe(
|
||||
"complete returns the complete application analysis Evidence inline. summary retains that complete Evidence in this session and returns its inspect_analysis_view summary; page modules or select items from normalized_result.parent_evidence_id.",
|
||||
),
|
||||
detail: analysisDetailSchema,
|
||||
});
|
||||
const applicationOutputSchema = evidenceResultOf(
|
||||
z.union([
|
||||
javascriptApplicationAnalysisResultSchema,
|
||||
analysisViewResultSchema,
|
||||
]),
|
||||
const applicationOutputSchema = analysisDetailOutputSchemaOf(
|
||||
javascriptApplicationAnalysisResultSchema,
|
||||
);
|
||||
const reconciliationOutputSchema = evidenceResultOf(
|
||||
javascriptRuntimeReconciliationResultSchema,
|
||||
|
|
|
|||
|
|
@ -6,10 +6,18 @@ import {
|
|||
inspectBinaryLayoutInputSchema,
|
||||
binaryLayoutSchema,
|
||||
} from "../../domain/native/binaryLayout.js";
|
||||
import {
|
||||
analysisDetailOutputSchemaOf,
|
||||
analysisDetailSchema,
|
||||
} from "../analysisDetail.js";
|
||||
import type { ToolContract } from "../toolContractTypes.js";
|
||||
import { toolContractMetadata } from "../toolEffects.js";
|
||||
import { evidenceResultOf } from "../toolOutputSchemaPrimitives.js";
|
||||
|
||||
/** Caller-selected delivery of completed binary layout Evidence. */
|
||||
export const inspectBinaryLayoutRequestSchema =
|
||||
inspectBinaryLayoutInputSchema.extend({ detail: analysisDetailSchema });
|
||||
|
||||
/** Offline binary diagnostics are independent of macOS process/UI providers. */
|
||||
export const BINARY_DIAGNOSTICS_TOOL_CONTRACTS = [
|
||||
{
|
||||
|
|
@ -17,14 +25,18 @@ export const BINARY_DIAGNOSTICS_TOOL_CONTRACTS = [
|
|||
...toolContractMetadata("inspect_binary_layout"),
|
||||
kind: "native-provider",
|
||||
description:
|
||||
"Inspect an explicit local binary without launching it as a host process. Returns file-backed section/segment ranges, linked addresses, original symbol/REL/RELA table identities, complete encoded RELR tables and upstream-derived offsets with unknown per-word locations/addends, raw name bytes, dependency/interpreter names and static mitigation inferences inline with artifact SHA-256 Evidence. Initial profile: ELF64 x86-64 little-endian EXEC/DYN/REL on Linux x64 via caller-supplied Python with unchanged pwntools 4.15.0, pyelftools 0.33 and Unicorn 2.1.2. Runtime addresses/library paths, overall relocation inventory completeness and complete derived GOT/PLT coverage remain unknown. Zero executable entry values mean absence; relocatable entries are not applicable. Upstream can emulate PLT instructions with Unicorn for derived static maps. Uses bounded complete output and owned temporary files/processes; malformed, unsupported or oversized objects return no partial success.",
|
||||
inputSchema: inspectBinaryLayoutInputSchema,
|
||||
outputSchema: evidenceResultOf(binaryLayoutSchema),
|
||||
"Inspect an explicit local binary without launching it as a host process. Returns file-backed section/segment ranges, linked addresses, original symbol/REL/RELA table identities, complete encoded RELR tables and upstream-derived offsets with unknown per-word locations/addends, raw name bytes, dependency/interpreter names and static mitigation inferences inline with artifact SHA-256 Evidence. Initial profile: ELF64 x86-64 little-endian EXEC/DYN/REL on Linux x64 via caller-supplied Python with unchanged pwntools 4.15.0, pyelftools 0.33 and Unicorn 2.1.2. Runtime addresses/library paths, overall relocation inventory completeness and complete derived GOT/PLT coverage remain unknown. Zero executable entry values mean absence; relocatable entries are not applicable. Upstream can emulate PLT instructions with Unicorn for derived static maps. Uses bounded complete output and owned temporary files/processes; malformed, unsupported or oversized objects return no partial success. Select detail summary to retain the complete layout Evidence in this session and receive only its summary view, then inspect sections, symbols, mitigations or linkage with inspect_analysis_view.",
|
||||
inputSchema: inspectBinaryLayoutRequestSchema,
|
||||
outputSchema: analysisDetailOutputSchemaOf(binaryLayoutSchema),
|
||||
examples: [
|
||||
{
|
||||
title: "Inspect ELF linked and file layout",
|
||||
input: { path: "/artifacts/application.elf" },
|
||||
},
|
||||
{
|
||||
title: "Retain the complete layout and return its summary view",
|
||||
input: { path: "/artifacts/application.elf", detail: "summary" },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
|
|
|
|||
|
|
@ -301,6 +301,7 @@ const registerConfiguredAnalysisTools = (
|
|||
options.binaryLayout ?? createBinaryLayoutService(environment),
|
||||
toolLogger,
|
||||
recordEvidence,
|
||||
evidenceById,
|
||||
);
|
||||
registerAnalysisViewTool(server, toolLogger, evidenceById, recordEvidence);
|
||||
registerEvmTools(
|
||||
|
|
|
|||
|
|
@ -1,6 +1,8 @@
|
|||
import type { EvidenceMcpServer } from "./EvidenceMcpServer.js";
|
||||
|
||||
import { summarizeRetainedAnalysis } from "../application/analysisView/AnalysisViewService.js";
|
||||
import type { BinaryLayoutService } from "../application/binaryDiagnostics/BinaryLayoutService.js";
|
||||
import type { EvidenceLookup } from "../application/EvidenceInputResolver.js";
|
||||
import type { EvidenceWriter } from "../application/investigation/InvestigationRecordPort.js";
|
||||
import { toolContract } from "../contracts/toolContracts.js";
|
||||
import type { Logger } from "pino";
|
||||
|
|
@ -13,15 +15,24 @@ export const registerBinaryDiagnosticsTools = (
|
|||
service: BinaryLayoutService,
|
||||
logger: Logger,
|
||||
recordEvidence?: EvidenceWriter["recordEvidence"],
|
||||
evidenceById?: EvidenceLookup,
|
||||
): void => {
|
||||
const contract = toolContract("inspect_binary_layout");
|
||||
server.registerTool(
|
||||
contract.name,
|
||||
toolRegistrationOptions(contract),
|
||||
async (input, context) => {
|
||||
const result = await logToolExecution(logger, contract.name, () =>
|
||||
service.inspect(input, { signal: context.mcpReq.signal }),
|
||||
);
|
||||
async ({ detail, ...request }, context) => {
|
||||
const result = await logToolExecution(logger, contract.name, async () => {
|
||||
const inspected = await service.inspect(request, {
|
||||
signal: context.mcpReq.signal,
|
||||
});
|
||||
return detail === "summary" && inspected.ok
|
||||
? summarizeRetainedAnalysis(inspected.value, {
|
||||
recordEvidence,
|
||||
evidenceById,
|
||||
})
|
||||
: inspected;
|
||||
});
|
||||
if (!result.ok) return server.delivery.toCallToolResult(result, contract);
|
||||
const recorded = recordEvidence?.(result.value);
|
||||
return server.delivery.toEvidenceToolResult(
|
||||
|
|
|
|||
|
|
@ -3,15 +3,22 @@ import { writeFile } from "node:fs/promises";
|
|||
import { join } from "node:path";
|
||||
import { expect, it, onTestFinished } from "vitest";
|
||||
|
||||
import { BinaryLayoutService } from "../../../src/application/binaryDiagnostics/BinaryLayoutService.js";
|
||||
import { toolContract } from "../../../src/contracts/toolContracts.js";
|
||||
import { ok } from "../../../src/domain/result.js";
|
||||
import { createServer } from "../../../src/server/createServer.js";
|
||||
import { analysisViewLayoutFixture } from "../../fixtures/analysisView.js";
|
||||
import { BINARY_LAYOUT_TEST_PROVIDER } from "../../fixtures/binaryDiagnostics/layout.js";
|
||||
import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
||||
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
|
||||
const contract = toolContract("analyze_javascript_application");
|
||||
|
||||
const connect = async (retainsEvidence: boolean) => {
|
||||
const connect = async (
|
||||
retainsEvidence: boolean,
|
||||
binaryLayout?: BinaryLayoutService,
|
||||
) => {
|
||||
const session = createTestBinarySession(() => {
|
||||
throw new Error("JavaScript analysis must not start a deep provider");
|
||||
});
|
||||
|
|
@ -26,6 +33,7 @@ const connect = async (retainsEvidence: boolean) => {
|
|||
},
|
||||
},
|
||||
},
|
||||
binaryLayout === undefined ? {} : { binaryLayout },
|
||||
);
|
||||
const client = new Client({ name: "analysis-summary-detail", version: "1" });
|
||||
onTestFinished(async () => {
|
||||
|
|
@ -123,3 +131,40 @@ it("refuses summary detail when the server cannot retain the complete Evidence",
|
|||
message: expect.stringContaining("detail complete"),
|
||||
});
|
||||
});
|
||||
|
||||
it("returns the summary of retained binary layout Evidence", async () => {
|
||||
const layout = analysisViewLayoutFixture();
|
||||
const client = await connect(
|
||||
true,
|
||||
new BinaryLayoutService({
|
||||
identity: BINARY_LAYOUT_TEST_PROVIDER,
|
||||
inspect: () => Promise.resolve(ok(layout)),
|
||||
}),
|
||||
);
|
||||
const layoutContract = toolContract("inspect_binary_layout");
|
||||
const summary = await client.callTool({
|
||||
name: layoutContract.name,
|
||||
arguments: { path: layout.artifact.path, detail: "summary" },
|
||||
});
|
||||
expect(summary.isError).not.toBe(true);
|
||||
const evidence = layoutContract.outputSchema.parse(summary.structuredContent);
|
||||
expect(evidence).toMatchObject({
|
||||
operation: "inspect_analysis_view",
|
||||
normalized_result: {
|
||||
kind: "summary",
|
||||
parent_operation: "inspect_binary_layout",
|
||||
},
|
||||
});
|
||||
const [parentId] = evidence.evidence_links;
|
||||
const sections = await client.callTool({
|
||||
name: "inspect_analysis_view",
|
||||
arguments: {
|
||||
source: { kind: "retained-evidence", evidence_id: parentId },
|
||||
view: { kind: "page", collection: "sections", offset: 0, limit: 8 },
|
||||
},
|
||||
});
|
||||
expect(sections.isError).not.toBe(true);
|
||||
expect(sections.structuredContent).toMatchObject({
|
||||
normalized_result: { kind: "page", parent_evidence_id: parentId },
|
||||
});
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in New Issue