mirror of
https://github.com/morluto/rea.git
synced 2026-10-26 17:35:11 +00:00
Add bounded, read-only inspection of packaged Electron fuse state for ELF, PE, and Mach-O applications through CLI and MCP workflows. Preserve artifact provenance and unknown values, resolve app bundles without execution, reject malformed or overlapping containers before scanning, and keep output compact for agent use. Co-authored-by: Matt Van Horn <455140+mvanhorn@users.noreply.github.com> Co-authored-by: morluto <76467478+morluto@users.noreply.github.com> Co-authored-by: N0zoM1z0 <161784452+N0zoM1z0@users.noreply.github.com> Co-authored-by: Kaoru0822 <208281322+kaoru0822-kitauji@users.noreply.github.com>
234 lines
8.3 KiB
TypeScript
234 lines
8.3 KiB
TypeScript
import type { ToolResultDelivery } from "./toolResult.js";
|
|
import type { EvidenceMcpServer } from "./EvidenceMcpServer.js";
|
|
import { summarizeRetainedAnalysis } from "../application/analysisView/AnalysisViewService.js";
|
|
import type { EvidenceLookup } from "../application/EvidenceInputResolver.js";
|
|
import type { EvidenceWriter } from "../application/investigation/InvestigationRecordPort.js";
|
|
import {
|
|
optionalProviderUnavailable,
|
|
type OptionalProviderLoadFailure,
|
|
} from "../application/OptionalObservationProviders.js";
|
|
import { err } from "../domain/result.js";
|
|
import type { ServerContext } from "@modelcontextprotocol/server";
|
|
|
|
import type { ElectronActiveObservationPort } from "../application/javascript/ElectronActiveObservationPort.js";
|
|
import { captureElectronScenario } from "../application/javascript/ElectronActiveObservationService.js";
|
|
import type { ElectronObservationPort } from "../application/javascript/ElectronObservationPort.js";
|
|
import {
|
|
inspectElectronPage,
|
|
listElectronTargets,
|
|
} from "../application/javascript/ElectronObservationService.js";
|
|
import { analyzeJavaScriptApplicationValidated } from "../application/javascript/JavaScriptApplicationService.js";
|
|
import type { ArtifactResourceScope } from "../artifacts/ArtifactResourceScope.js";
|
|
import { reconcileJavaScriptRuntimeEvidenceValidated } from "../application/javascript/JavaScriptRuntimeReconciliationService.js";
|
|
import type { ProgressReporter } from "../application/ProgressReporter.js";
|
|
import type { ElectronFuseService } from "../application/electron/ElectronFuseService.js";
|
|
import { toolContract } from "../contracts/toolContracts.js";
|
|
import type { ToolContract } from "../contracts/toolContractTypes.js";
|
|
import type { AnalysisError } from "../domain/analysisErrorBase.js";
|
|
import type { Evidence } from "../domain/evidence.js";
|
|
import type { Result } from "../domain/result.js";
|
|
import type { Logger } from "pino";
|
|
import { mcpProgressReporter } from "./mcpProgress.js";
|
|
import { logToolExecution } from "./toolLogging.js";
|
|
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
|
import type { WithAdmittedAnalysis } from "./analysisAdmission.js";
|
|
import { runAdmittedToolOperation } from "./admittedToolOperation.js";
|
|
import { createEvidenceToolHandler } from "./contractToolHandler.js";
|
|
|
|
interface ElectronToolRegistration {
|
|
readonly artifactResources: ArtifactResourceScope;
|
|
readonly logger: Logger;
|
|
readonly observationLoadFailure?: OptionalProviderLoadFailure | undefined;
|
|
readonly activeLoadFailure?: OptionalProviderLoadFailure | undefined;
|
|
readonly electron: ElectronObservationPort | undefined;
|
|
readonly electronActive: ElectronActiveObservationPort | undefined;
|
|
readonly electronFuses: ElectronFuseService;
|
|
readonly recordEvidence: EvidenceWriter["recordEvidence"] | undefined;
|
|
readonly evidenceById: EvidenceLookup | undefined;
|
|
readonly withAdmittedAnalysis?: WithAdmittedAnalysis;
|
|
}
|
|
|
|
interface ElectronToolContext {
|
|
readonly signal: AbortSignal;
|
|
readonly progress: ProgressReporter;
|
|
}
|
|
|
|
/** Register Electron tools even when a provider is absent. */
|
|
// oxlint-disable-next-line max-lines-per-function -- direct SDK calls retain each schema-handler type correlation.
|
|
export const registerElectronTools = (
|
|
server: EvidenceMcpServer,
|
|
options: ElectronToolRegistration,
|
|
): void => {
|
|
const registration = { ...options, delivery: server.delivery, server };
|
|
registerElectronFuseTool(server, options);
|
|
const listContract = toolContract("list_electron_targets");
|
|
const inspectContract = toolContract("inspect_electron_page");
|
|
const analyzeContract = toolContract("analyze_javascript_application");
|
|
const reconcileContract = toolContract("reconcile_javascript_runtime");
|
|
const activeContract = toolContract("capture_electron_scenario");
|
|
|
|
server.registerTool(
|
|
listContract.name,
|
|
toolRegistrationOptions(listContract),
|
|
(input, context) =>
|
|
runElectronTool(
|
|
registration,
|
|
listContract,
|
|
{ input, context },
|
|
(parsed, { signal }) =>
|
|
listElectronTargets(options.electron, parsed, {
|
|
signal,
|
|
}),
|
|
),
|
|
);
|
|
server.registerTool(
|
|
inspectContract.name,
|
|
toolRegistrationOptions(inspectContract),
|
|
(input, context) =>
|
|
runElectronTool(
|
|
registration,
|
|
inspectContract,
|
|
{ input, context },
|
|
(parsed, { signal, progress }) =>
|
|
inspectElectronPage(options.electron, parsed, {
|
|
signal,
|
|
progress,
|
|
}),
|
|
),
|
|
);
|
|
server.registerTool(
|
|
analyzeContract.name,
|
|
toolRegistrationOptions(analyzeContract),
|
|
(input, context) =>
|
|
runElectronTool(
|
|
registration,
|
|
analyzeContract,
|
|
{ input, context },
|
|
async ({ detail, ...request }, { signal, progress }) => {
|
|
const analyzed = await analyzeJavaScriptApplicationValidated(
|
|
request,
|
|
options.artifactResources,
|
|
{ signal, progress },
|
|
);
|
|
return detail === "summary" && analyzed.ok
|
|
? summarizeRetainedAnalysis(analyzed.value, options)
|
|
: analyzed;
|
|
},
|
|
),
|
|
);
|
|
server.registerTool(
|
|
reconcileContract.name,
|
|
toolRegistrationOptions(reconcileContract),
|
|
(input, context) =>
|
|
runElectronTool(
|
|
registration,
|
|
reconcileContract,
|
|
{ input, context },
|
|
(parsed) =>
|
|
Promise.resolve(reconcileJavaScriptRuntimeEvidenceValidated(parsed)),
|
|
),
|
|
);
|
|
server.registerTool(
|
|
activeContract.name,
|
|
toolRegistrationOptions(activeContract),
|
|
(input, context) =>
|
|
runElectronTool(
|
|
registration,
|
|
activeContract,
|
|
{ input, context },
|
|
(parsed, { signal, progress }) =>
|
|
captureElectronScenario(options.electronActive, parsed, {
|
|
signal,
|
|
progress,
|
|
}),
|
|
),
|
|
);
|
|
};
|
|
|
|
const registerElectronFuseTool = (
|
|
server: EvidenceMcpServer,
|
|
options: ElectronToolRegistration,
|
|
): void => {
|
|
const contract = toolContract("inspect_electron_fuses");
|
|
const handler = createEvidenceToolHandler(
|
|
server,
|
|
(input, execution) => options.electronFuses.inspect(input, execution),
|
|
options.logger,
|
|
options.recordEvidence,
|
|
options.withAdmittedAnalysis,
|
|
);
|
|
server.registerTool(
|
|
contract.name,
|
|
toolRegistrationOptions(contract),
|
|
handler(contract),
|
|
);
|
|
};
|
|
|
|
const runElectronTool = async <Input>(
|
|
options: ElectronToolRegistration & {
|
|
readonly delivery: ToolResultDelivery;
|
|
readonly server: EvidenceMcpServer;
|
|
},
|
|
contract: ToolContract,
|
|
request: {
|
|
readonly input: Input;
|
|
readonly context: ServerContext;
|
|
},
|
|
execute: (
|
|
input: Input,
|
|
context: ElectronToolContext,
|
|
) => Promise<Result<Evidence, AnalysisError>>,
|
|
) => {
|
|
const { input, context } = request;
|
|
const failure =
|
|
contract.name === "capture_electron_scenario"
|
|
? options.activeLoadFailure
|
|
: contract.name === "list_electron_targets" ||
|
|
contract.name === "inspect_electron_page"
|
|
? options.observationLoadFailure
|
|
: undefined;
|
|
if (failure !== undefined)
|
|
return options.delivery.toCallToolResult(
|
|
err(optionalProviderUnavailable(failure, contract.name)),
|
|
contract,
|
|
);
|
|
return runAdmittedToolOperation(
|
|
options.server,
|
|
options.withAdmittedAnalysis,
|
|
contract.name,
|
|
context.mcpReq.signal,
|
|
async () => {
|
|
const result = await logToolExecution(options.logger, contract.name, () =>
|
|
execute(input, {
|
|
signal: context.mcpReq.signal,
|
|
progress: mcpProgressReporter(context),
|
|
}),
|
|
);
|
|
if (!result.ok) {
|
|
const partial = result.error.partialObservation;
|
|
if (
|
|
partial !== undefined &&
|
|
"evidence_id" in partial &&
|
|
typeof partial.evidence_id === "string"
|
|
) {
|
|
const recorded = options.recordEvidence?.(partial);
|
|
if (recorded !== undefined)
|
|
return options.delivery.toRecordedPartialErrorToolResult(
|
|
result.error,
|
|
partial.evidence_id,
|
|
recorded,
|
|
contract.returnsTargetContent,
|
|
);
|
|
}
|
|
return options.delivery.toCallToolResult(result, contract);
|
|
}
|
|
const recorded = options.recordEvidence?.(result.value);
|
|
return options.delivery.toEvidenceToolResult(
|
|
result.value,
|
|
contract,
|
|
recorded,
|
|
);
|
|
},
|
|
);
|
|
};
|