mirror of
https://github.com/morluto/rea
synced 2026-10-25 22:08:11 -04:00
refactor(inputs): own absolute path validation in shared contracts
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
import { closeOwnedWebResources } from "./closeOwnedWebResources.js";
|
||||
import { isDeepStrictEqual } from "node:util";
|
||||
import { isAbsolute } from "node:path";
|
||||
import { z } from "zod";
|
||||
import type { AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
import {
|
||||
@@ -94,15 +93,6 @@ export class WebModuleTraceService {
|
||||
}),
|
||||
);
|
||||
const input = parsed.data;
|
||||
for (const [path, value] of [
|
||||
["manifest_path", input.manifest_path],
|
||||
["import_map.path", input.import_map?.path],
|
||||
] as const)
|
||||
if (value !== undefined && !isAbsolute(value))
|
||||
return invalid(
|
||||
path.split("."),
|
||||
"Expected an absolute filesystem path on this host.",
|
||||
);
|
||||
for (const [path, value] of [
|
||||
["importer_url", input.importer_url],
|
||||
["import_map.base_url", input.import_map?.base_url],
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { redactExplicitFailure } from "../domain/explicitSensitiveFailure.js";
|
||||
import { excludeCaptureCoordinates } from "../domain/webNetworkCaptureCoordinates.js";
|
||||
import { redactExplicitText } from "../domain/explicitSensitiveValues.js";
|
||||
import { isAbsolute } from "node:path";
|
||||
import type { ExecutionOptions } from "./AnalysisProvider.js";
|
||||
import type { WebNetworkCapturePort } from "./WebNetworkCapturePort.js";
|
||||
import type { AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
@@ -57,16 +56,6 @@ export class WebNetworkCaptureService {
|
||||
}),
|
||||
);
|
||||
const input = parsed.data;
|
||||
if (!isAbsolute(input.capture_path))
|
||||
return failure(
|
||||
new AnalysisInputError(OPERATION, undefined, [
|
||||
{
|
||||
path: ["capture_path"],
|
||||
reason: "invalid_format",
|
||||
message: "Expected an absolute filesystem path on this host.",
|
||||
},
|
||||
]),
|
||||
);
|
||||
if (
|
||||
input.record_ordinals !== undefined &&
|
||||
new Set(input.record_ordinals).size !== input.record_ordinals.length
|
||||
@@ -91,19 +80,6 @@ export class WebNetworkCaptureService {
|
||||
},
|
||||
]),
|
||||
);
|
||||
if (
|
||||
input.protobuf?.schema !== undefined &&
|
||||
!isAbsolute(input.protobuf.schema.descriptor_set_path)
|
||||
)
|
||||
return failure(
|
||||
new AnalysisInputError(OPERATION, undefined, [
|
||||
{
|
||||
path: ["protobuf", "schema", "descriptor_set_path"],
|
||||
reason: "invalid_format",
|
||||
message: "Expected an absolute descriptor-set path on this host.",
|
||||
},
|
||||
]),
|
||||
);
|
||||
const loaded = await this.port.inspect(input, options);
|
||||
if (!loaded.ok) {
|
||||
const partial = webNetworkCaptureSchema.safeParse(
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import { closeOwnedWebResources } from "./closeOwnedWebResources.js";
|
||||
import { createHash } from "node:crypto";
|
||||
import { isAbsolute } from "node:path";
|
||||
import { z } from "zod";
|
||||
import type { AnalysisError } from "../domain/analysisErrorBase.js";
|
||||
import {
|
||||
@@ -209,15 +208,6 @@ const invalid = (
|
||||
const admitContext = (
|
||||
input: WebSourceLocationInput,
|
||||
): Result<null, AnalysisError> => {
|
||||
for (const [path, value] of [
|
||||
["manifest_path", input.manifest_path],
|
||||
["source_map.path", input.source_map.path],
|
||||
] as const)
|
||||
if (!isAbsolute(value))
|
||||
return invalid(
|
||||
path.split("."),
|
||||
"Expected an absolute filesystem path on this host.",
|
||||
);
|
||||
if (!URL.canParse(input.source_map.url))
|
||||
return invalid(
|
||||
["source_map", "url"],
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
import { isAbsolute } from "node:path";
|
||||
import type { ExecutionOptions } from "../AnalysisProvider.js";
|
||||
import type { DesktopApplicationPort } from "./DesktopApplicationPort.js";
|
||||
import { ARTIFACT_GRAPH_PROVIDER } from "../InvestigationProviders.js";
|
||||
@@ -41,16 +40,6 @@ export class DesktopApplicationService {
|
||||
cause: parsed.error,
|
||||
}),
|
||||
);
|
||||
if (!isAbsolute(parsed.data.path))
|
||||
return err(
|
||||
new AnalysisInputError(operation, undefined, [
|
||||
{
|
||||
path: ["path"],
|
||||
reason: "invalid_format",
|
||||
message: "Expected an absolute directory path on this host.",
|
||||
},
|
||||
]),
|
||||
);
|
||||
const result = await this.reader.inspect(parsed.data, options);
|
||||
if (!result.ok) return result;
|
||||
const report = desktopApplicationSchema.safeParse(result.value);
|
||||
|
||||
@@ -25,10 +25,18 @@ it.each([
|
||||
throw new Error("must not run");
|
||||
},
|
||||
});
|
||||
expect(await service.inspect(input)).toMatchObject({
|
||||
const result = await service.inspect(input);
|
||||
expect(result).toMatchObject({
|
||||
ok: false,
|
||||
error: { _tag: "AnalysisInputError" },
|
||||
});
|
||||
if (input.path === "relative.elf")
|
||||
expect(result).toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
issues: [{ path: ["path"], reason: "invalid_format" }],
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { isAbsolute } from "node:path";
|
||||
import type { ExecutionOptions } from "../AnalysisProvider.js";
|
||||
import type { BinaryLayoutPort } from "./BinaryLayoutPort.js";
|
||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisInputError,
|
||||
AnalysisOutputError,
|
||||
} from "../../domain/analysisErrorCore.js";
|
||||
import { createEvidence, type Evidence } from "../../domain/evidence.js";
|
||||
@@ -66,16 +64,6 @@ export class BinaryLayoutService {
|
||||
cause: input.error,
|
||||
}),
|
||||
);
|
||||
if (!isAbsolute(input.data.path))
|
||||
return err(
|
||||
new AnalysisInputError(OPERATION, undefined, [
|
||||
{
|
||||
path: ["path"],
|
||||
reason: "invalid_format",
|
||||
message: "Expected an absolute filesystem path on this host.",
|
||||
},
|
||||
]),
|
||||
);
|
||||
const inspected = await inspect(input.data);
|
||||
if (!inspected.ok) return inspected;
|
||||
const diagnostics = binaryLayoutSchema.shape.diagnostics.safeParse(
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { isAbsolute } from "node:path";
|
||||
import type { ExecutionOptions } from "../AnalysisProvider.js";
|
||||
import type { PeResourcesPort } from "./PeResourcesPort.js";
|
||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisInputError,
|
||||
AnalysisOutputError,
|
||||
} from "../../domain/analysisErrorCore.js";
|
||||
import { createEvidence, type Evidence } from "../../domain/evidence.js";
|
||||
@@ -47,16 +45,6 @@ export class PeResourcesService {
|
||||
cause: input.error,
|
||||
}),
|
||||
);
|
||||
if (!isAbsolute(input.data.path))
|
||||
return err(
|
||||
new AnalysisInputError(operation, undefined, [
|
||||
{
|
||||
path: ["path"],
|
||||
reason: "invalid_format",
|
||||
message: "Expected an absolute filesystem path on this host.",
|
||||
},
|
||||
]),
|
||||
);
|
||||
const result = await this.provider.inspect(input.data, options);
|
||||
if (!result.ok) return result;
|
||||
const report = peResourcesSchema.safeParse(result.value);
|
||||
|
||||
@@ -70,10 +70,18 @@ it.each([
|
||||
throw new Error("must not run");
|
||||
},
|
||||
});
|
||||
expect(await service.inspect(input)).toMatchObject({
|
||||
const result = await service.inspect(input);
|
||||
expect(result).toMatchObject({
|
||||
ok: false,
|
||||
error: { _tag: "AnalysisInputError" },
|
||||
});
|
||||
if (input.path === "relative.elf")
|
||||
expect(result).toMatchObject({
|
||||
ok: false,
|
||||
error: {
|
||||
issues: [{ path: ["path"], reason: "invalid_format" }],
|
||||
},
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { isAbsolute } from "node:path";
|
||||
import type { ExecutionOptions } from "../AnalysisProvider.js";
|
||||
import type { RecordedCrashPort } from "./RecordedCrashPort.js";
|
||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisInputError,
|
||||
AnalysisOutputError,
|
||||
} from "../../domain/analysisErrorCore.js";
|
||||
import { createEvidence, type Evidence } from "../../domain/evidence.js";
|
||||
@@ -67,16 +65,6 @@ export class RecordedCrashService {
|
||||
cause: input.error,
|
||||
}),
|
||||
);
|
||||
if (!isAbsolute(input.data.path))
|
||||
return err(
|
||||
new AnalysisInputError(OPERATION, undefined, [
|
||||
{
|
||||
path: ["path"],
|
||||
reason: "invalid_format",
|
||||
message: "Expected an absolute filesystem path on this host.",
|
||||
},
|
||||
]),
|
||||
);
|
||||
const inspected = await inspect(input.data);
|
||||
if (!inspected.ok) return inspected;
|
||||
const diagnostics = recordedCrashSchema.shape.diagnostics.safeParse(
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
type InspectPacketFieldsInput,
|
||||
} from "../../domain/captures/packetFields.js";
|
||||
import type { JsonValue } from "../../domain/jsonValue.js";
|
||||
import { isAbsolute } from "node:path";
|
||||
import type {
|
||||
AnalysisExecution,
|
||||
ExecutionOptions,
|
||||
@@ -12,7 +11,6 @@ import type {
|
||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisInputError,
|
||||
AnalysisOutputError,
|
||||
} from "../../domain/analysisErrorCore.js";
|
||||
import { createEvidence, type Evidence } from "../../domain/evidence.js";
|
||||
@@ -59,16 +57,6 @@ export class PacketCaptureService {
|
||||
cause: input.error,
|
||||
}),
|
||||
);
|
||||
if (!isAbsolute(input.data.path))
|
||||
return err(
|
||||
new AnalysisInputError(operation, undefined, [
|
||||
{
|
||||
path: ["path"],
|
||||
reason: "invalid_format",
|
||||
message: "Expected an absolute local path.",
|
||||
},
|
||||
]),
|
||||
);
|
||||
const decoded = await this.provider.inspect(input.data, options);
|
||||
if (!decoded.ok) return decoded;
|
||||
const execution = decoded.value;
|
||||
|
||||
@@ -1,11 +1,8 @@
|
||||
import { isAbsolute } from "node:path";
|
||||
|
||||
import type { ExecutionOptions } from "../AnalysisProvider.js";
|
||||
import type { ElectronFusePort } from "./ElectronFusePort.js";
|
||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisInputError,
|
||||
AnalysisOutputError,
|
||||
} from "../../domain/analysisErrorCore.js";
|
||||
import {
|
||||
@@ -45,16 +42,6 @@ export class ElectronFuseService {
|
||||
cause: input.error,
|
||||
}),
|
||||
);
|
||||
if (!isAbsolute(input.data.path))
|
||||
return err(
|
||||
new AnalysisInputError(OPERATION, undefined, [
|
||||
{
|
||||
path: ["path"],
|
||||
reason: "invalid_format",
|
||||
message: "Expected an absolute filesystem path on this host.",
|
||||
},
|
||||
]),
|
||||
);
|
||||
const inspected = await this.provider.inspect(input.data, options);
|
||||
if (!inspected.ok) return inspected;
|
||||
const report = electronFuseReportSchema.safeParse(inspected.value);
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
import { isAbsolute } from "node:path";
|
||||
import { z } from "zod";
|
||||
import type { ExecutionOptions } from "../AnalysisProvider.js";
|
||||
import type { EvmInterfacePort } from "./EvmInterfacePort.js";
|
||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisInputError,
|
||||
AnalysisOutputError,
|
||||
} from "../../domain/analysisErrorCore.js";
|
||||
import type { Evidence } from "../../domain/evidence.js";
|
||||
@@ -47,16 +45,6 @@ export class EvmInterfaceService {
|
||||
cause: input.error,
|
||||
}),
|
||||
);
|
||||
if (!isAbsolute(input.data.path))
|
||||
return err(
|
||||
new AnalysisInputError(OPERATION, undefined, [
|
||||
{
|
||||
path: ["path"],
|
||||
reason: "invalid_format",
|
||||
message: "Expected an absolute filesystem path on this host.",
|
||||
},
|
||||
]),
|
||||
);
|
||||
const inspected = await this.provider.inspect(input.data, options);
|
||||
if (!inspected.ok) return inspected;
|
||||
const diagnostics = diagnosticsSchema.safeParse(inspected.value.result);
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { isAbsolute } from "node:path";
|
||||
import type { ExecutionOptions } from "../AnalysisProvider.js";
|
||||
import type { GoBinaryPort } from "./GoBinaryPort.js";
|
||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisInputError,
|
||||
AnalysisOutputError,
|
||||
} from "../../domain/analysisErrorCore.js";
|
||||
import { createEvidence, type Evidence } from "../../domain/evidence.js";
|
||||
@@ -53,16 +51,6 @@ export class GoBinaryService {
|
||||
cause: input.error,
|
||||
}),
|
||||
);
|
||||
if (!isAbsolute(input.data.path))
|
||||
return err(
|
||||
new AnalysisInputError(OPERATION, undefined, [
|
||||
{
|
||||
path: ["path"],
|
||||
reason: "invalid_format",
|
||||
message: "Expected an absolute filesystem path on this host.",
|
||||
},
|
||||
]),
|
||||
);
|
||||
const { detail, ...providerInput } = input.data;
|
||||
if (detail === "summary") {
|
||||
const ready = requireAnalysisViewRetention(OPERATION, retention);
|
||||
|
||||
@@ -5,7 +5,6 @@ import type {
|
||||
Il2cppSeedWriterPort,
|
||||
Il2cppSeedReceipt,
|
||||
} from "./Il2cppSeedWriterPort.js";
|
||||
import { isAbsolute } from "node:path";
|
||||
import type { ExecutionOptions } from "../AnalysisProvider.js";
|
||||
import {
|
||||
requireAnalysisViewRetention,
|
||||
@@ -111,19 +110,6 @@ export class Il2cppMetadataService {
|
||||
cause: input.error,
|
||||
}),
|
||||
);
|
||||
for (const field of ["path", "binary_path"] as const) {
|
||||
const value = input.data[field];
|
||||
if (value !== undefined && !isAbsolute(value))
|
||||
return err(
|
||||
new AnalysisInputError(OPERATION, undefined, [
|
||||
{
|
||||
path: [field],
|
||||
reason: "invalid_format",
|
||||
message: "Expected an absolute filesystem path on this host.",
|
||||
},
|
||||
]),
|
||||
);
|
||||
}
|
||||
if (
|
||||
input.data.binary_architecture !== undefined &&
|
||||
input.data.binary_path === undefined
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { isAbsolute } from "node:path";
|
||||
import type { ExecutionOptions } from "../AnalysisProvider.js";
|
||||
import type { RustBinaryPort } from "./RustBinaryPort.js";
|
||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisInputError,
|
||||
AnalysisOutputError,
|
||||
} from "../../domain/analysisErrorCore.js";
|
||||
import { createEvidence, type Evidence } from "../../domain/evidence.js";
|
||||
@@ -52,16 +50,6 @@ export class RustBinaryService {
|
||||
cause: input.error,
|
||||
}),
|
||||
);
|
||||
if (!isAbsolute(input.data.path))
|
||||
return err(
|
||||
new AnalysisInputError(OPERATION, undefined, [
|
||||
{
|
||||
path: ["path"],
|
||||
reason: "invalid_format",
|
||||
message: "Expected an absolute filesystem path on this host.",
|
||||
},
|
||||
]),
|
||||
);
|
||||
const { detail, view, ...parameters } = input.data;
|
||||
const compact = detail === "summary" || view !== undefined;
|
||||
if (compact) {
|
||||
|
||||
@@ -4,7 +4,6 @@ import {
|
||||
type InspectSignalDecodersInput,
|
||||
} from "../../domain/signals/signalDecoders.js";
|
||||
import type { JsonValue } from "../../domain/jsonValue.js";
|
||||
import { isAbsolute } from "node:path";
|
||||
import type {
|
||||
AnalysisExecution,
|
||||
ExecutionOptions,
|
||||
@@ -12,7 +11,6 @@ import type {
|
||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisInputError,
|
||||
AnalysisOutputError,
|
||||
} from "../../domain/analysisErrorCore.js";
|
||||
import { createEvidence, type Evidence } from "../../domain/evidence.js";
|
||||
@@ -59,16 +57,6 @@ export class SignalRecordingService {
|
||||
cause: input.error,
|
||||
}),
|
||||
);
|
||||
if (!isAbsolute(input.data.path))
|
||||
return err(
|
||||
new AnalysisInputError(operation, undefined, [
|
||||
{
|
||||
path: ["path"],
|
||||
reason: "invalid_format",
|
||||
message: "Expected an absolute local path.",
|
||||
},
|
||||
]),
|
||||
);
|
||||
const decoded = await this.provider.decode(input.data, options);
|
||||
if (!decoded.ok) return decoded;
|
||||
const execution = decoded.value;
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
import { isAbsolute } from "node:path";
|
||||
import type { ExecutionOptions } from "../AnalysisProvider.js";
|
||||
import type { SqliteDatabasePort } from "./SqliteDatabasePort.js";
|
||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisInputError,
|
||||
AnalysisOutputError,
|
||||
} from "../../domain/analysisErrorCore.js";
|
||||
import {
|
||||
@@ -87,16 +85,6 @@ export class SqliteDatabaseService {
|
||||
cause: input.error,
|
||||
}),
|
||||
);
|
||||
if (!isAbsolute(input.data.path))
|
||||
return err(
|
||||
new AnalysisInputError(OPERATION, undefined, [
|
||||
{
|
||||
path: ["path"],
|
||||
reason: "invalid_format",
|
||||
message: "Expected an absolute filesystem path on this host.",
|
||||
},
|
||||
]),
|
||||
);
|
||||
const inspected = await this.provider.inspect(input.data, options);
|
||||
if (!inspected.ok) return inspected;
|
||||
const execution = inspected.value;
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import { isAbsolute } from "node:path";
|
||||
import type { ExecutionOptions } from "../AnalysisProvider.js";
|
||||
import type { WasmArtifactPort } from "./WasmArtifactPort.js";
|
||||
import type { AnalysisError } from "../../domain/analysisErrorBase.js";
|
||||
import {
|
||||
AnalysisCancelledError,
|
||||
AnalysisInputError,
|
||||
AnalysisOutputError,
|
||||
type AnalysisInputIssue,
|
||||
} from "../../domain/analysisErrorCore.js";
|
||||
import { createEvidence, type Evidence } from "../../domain/evidence.js";
|
||||
import {
|
||||
@@ -48,29 +45,6 @@ export class WasmArtifactService {
|
||||
cause: input.error,
|
||||
}),
|
||||
);
|
||||
const pathIssues = [
|
||||
{ value: input.data.path, path: ["path"] },
|
||||
...input.data.glue_paths.map((value, index) => ({
|
||||
value,
|
||||
path: ["glue_paths", index],
|
||||
})),
|
||||
...input.data.candidate_paths.map((value, index) => ({
|
||||
value,
|
||||
path: ["candidate_paths", index],
|
||||
})),
|
||||
].flatMap<AnalysisInputIssue>(({ value, path }) =>
|
||||
isAbsolute(value)
|
||||
? []
|
||||
: [
|
||||
{
|
||||
path,
|
||||
reason: "invalid_format",
|
||||
message: "Expected an absolute filesystem path on this host.",
|
||||
},
|
||||
],
|
||||
);
|
||||
if (pathIssues.length > 0)
|
||||
return err(new AnalysisInputError(OPERATION, undefined, pathIssues));
|
||||
const inspected = await this.provider.inspect(input.data, options);
|
||||
if (!inspected.ok) return inspected;
|
||||
const execution = inspected.value;
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
import { expect, it } from "vitest";
|
||||
import { inspectGoBinaryInputSchema } from "./go/goBinary.js";
|
||||
import { inspectRustBinaryRequestSchema } from "./rust/rustBinaryQuery.js";
|
||||
import { inspectEvmInterfaceInputSchema } from "./evm/evmInterface.js";
|
||||
import { inspectSqliteDatabaseInputSchema } from "./sqlite/sqliteDatabase.js";
|
||||
import { inspectPeResourcesInputSchema } from "./native/peResources.js";
|
||||
import { inspectElectronFusesInputSchema } from "./electron/electronFuses.js";
|
||||
import { inspectPacketCaptureInputSchema } from "./captures/packetCapture.js";
|
||||
import { decodeSignalRecordingInputSchema } from "./signals/signalRecording.js";
|
||||
|
||||
it.each([
|
||||
["Go binary", inspectGoBinaryInputSchema],
|
||||
["Rust binary", inspectRustBinaryRequestSchema],
|
||||
["EVM interface", inspectEvmInterfaceInputSchema],
|
||||
["SQLite database", inspectSqliteDatabaseInputSchema],
|
||||
["PE resources", inspectPeResourcesInputSchema],
|
||||
["Electron fuses", inspectElectronFusesInputSchema],
|
||||
["packet capture", inspectPacketCaptureInputSchema],
|
||||
["signal recording", decodeSignalRecordingInputSchema],
|
||||
] as const)(
|
||||
"reports relative %s paths as a path format issue",
|
||||
(_name, schema) => {
|
||||
const parsed = schema.safeParse({ path: "relative/input" });
|
||||
expect(parsed.success).toBe(false);
|
||||
if (!parsed.success)
|
||||
expect(parsed.error.issues).toEqual(
|
||||
expect.arrayContaining([
|
||||
expect.objectContaining({ code: "invalid_format", path: ["path"] }),
|
||||
]),
|
||||
);
|
||||
},
|
||||
);
|
||||
@@ -1,6 +1,9 @@
|
||||
import { z } from "zod";
|
||||
import { fixedTupleSchema } from "../fixedTupleSchema.js";
|
||||
import { localPathStringSchema } from "../localPath.js";
|
||||
import {
|
||||
absoluteLocalPathStringSchema,
|
||||
localPathStringSchema,
|
||||
} from "../localPath.js";
|
||||
|
||||
const argument = z
|
||||
.string()
|
||||
@@ -82,10 +85,14 @@ export const packetCaptureSelectorBudgetIssue = {
|
||||
message:
|
||||
"Packet selectors exceed the 32 KiB encoded argument budget; reduce fields, decode-as rules or filter text.",
|
||||
};
|
||||
export const inspectPacketCaptureInputSchema = captureInputShape.refine(
|
||||
const packetCaptureSelectionSchema = captureInputShape.refine(
|
||||
withinPacketCaptureSelectorBudget,
|
||||
packetCaptureSelectorBudgetIssue,
|
||||
);
|
||||
export const inspectPacketCaptureInputSchema =
|
||||
packetCaptureSelectionSchema.safeExtend({
|
||||
path: absoluteLocalPathStringSchema.describe("Absolute local capture path"),
|
||||
});
|
||||
export const packetCaptureOptionsSchema = captureInputShape
|
||||
.omit({ path: true })
|
||||
.refine(withinPacketCaptureSelectorBudget, packetCaptureSelectorBudgetIssue);
|
||||
@@ -106,7 +113,7 @@ export const packetCaptureSchema = z
|
||||
sha256: z.string().regex(/^[a-f0-9]{64}$/),
|
||||
bytes: z.number().int().nonnegative(),
|
||||
}),
|
||||
selection: inspectPacketCaptureInputSchema,
|
||||
selection: packetCaptureSelectionSchema,
|
||||
packets: z.array(packetFieldsSchema),
|
||||
output_window: z.strictObject({
|
||||
matching_packets: z
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { z } from "zod";
|
||||
import { digestSchema, prefixedDigestSchema } from "./digests.js";
|
||||
import { localPathStringSchema } from "./localPath.js";
|
||||
import {
|
||||
absoluteLocalPathStringSchema,
|
||||
localPathStringSchema,
|
||||
} from "./localPath.js";
|
||||
import { artifactTraversalOmissionKindSchema } from "./artifactGraph.js";
|
||||
|
||||
export const desktopFamilySchema = z.enum([
|
||||
@@ -13,7 +16,7 @@ export const desktopFamilySchema = z.enum([
|
||||
"electron",
|
||||
]);
|
||||
export const inspectDesktopApplicationInputSchema = z.strictObject({
|
||||
path: localPathStringSchema,
|
||||
path: absoluteLocalPathStringSchema,
|
||||
max_entries: z.number().int().positive().max(100_000).default(20_000),
|
||||
max_probe_bytes: z
|
||||
.number()
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { digestSchema } from "../digests.js";
|
||||
import { localPathStringSchema } from "../localPath.js";
|
||||
import {
|
||||
absoluteLocalPathStringSchema,
|
||||
localPathStringSchema,
|
||||
} from "../localPath.js";
|
||||
|
||||
/** ASCII sentinel that introduces an Electron fuse wire in an Electron binary. */
|
||||
export const ELECTRON_FUSE_SENTINEL = "dL7pKGdnNz796PbbjQWNKmHXBZaB9tsX";
|
||||
@@ -158,7 +161,7 @@ export const electronFuseReportSchema = z
|
||||
});
|
||||
|
||||
export const inspectElectronFusesInputSchema = z.strictObject({
|
||||
path: localPathStringSchema.describe(
|
||||
path: absoluteLocalPathStringSchema.describe(
|
||||
"Absolute filesystem path to a macOS .app bundle, Electron Framework binary, Windows PE executable, or Linux ELF executable",
|
||||
),
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { localPathStringSchema } from "../localPath.js";
|
||||
import { absoluteLocalPathStringSchema } from "../localPath.js";
|
||||
import { solidityMetadataSchema } from "./solidityMetadata.js";
|
||||
|
||||
const byteOffset = z.number().int().nonnegative();
|
||||
@@ -12,7 +12,7 @@ export const MAX_EVM_TIMEOUT_MS = 2_147_483_647;
|
||||
|
||||
/** Select the carrier representation explicitly; no RPC or deployed-contract lookup is implied. */
|
||||
export const inspectEvmInterfaceInputSchema = z.strictObject({
|
||||
path: localPathStringSchema.describe(
|
||||
path: absoluteLocalPathStringSchema.describe(
|
||||
"Absolute path to caller-selected local bytecode carrier",
|
||||
),
|
||||
encoding: z
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
import { z } from "zod";
|
||||
import { BINARY_ARCHITECTURES } from "../binaryTargetTypes.js";
|
||||
import { digestSchema } from "../digests.js";
|
||||
import { localPathStringSchema } from "../localPath.js";
|
||||
import {
|
||||
absoluteLocalPathStringSchema,
|
||||
localPathStringSchema,
|
||||
} from "../localPath.js";
|
||||
import { canonicalBase64Schema } from "../stringPatterns.js";
|
||||
|
||||
/** Map reader architecture names into Evidence identity; unknown names stay unknown. */
|
||||
@@ -46,7 +49,7 @@ export const goModuleMetadataSchema = z.strictObject({
|
||||
|
||||
/** One exact local artifact, independent of any active disassembler target. */
|
||||
export const inspectGoBinaryInputSchema = z.strictObject({
|
||||
path: localPathStringSchema.describe(
|
||||
path: absoluteLocalPathStringSchema.describe(
|
||||
"Absolute filesystem path to the selected executable or library",
|
||||
),
|
||||
detail: z
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { z } from "zod";
|
||||
import { digestSchema } from "../digests.js";
|
||||
import { localPathStringSchema } from "../localPath.js";
|
||||
import {
|
||||
absoluteLocalPathStringSchema,
|
||||
localPathStringSchema,
|
||||
} from "../localPath.js";
|
||||
import { canonicalBase64Schema } from "../stringPatterns.js";
|
||||
|
||||
/** Unsigned little-endian signature observed at file offset zero. */
|
||||
@@ -86,7 +89,7 @@ const indexWidth = z.union([z.literal(1), z.literal(2), z.literal(4)]);
|
||||
|
||||
/** One explicit local file; detail selects delivery, never decoding coverage. */
|
||||
export const inspectIl2cppMetadataInputSchema = z.strictObject({
|
||||
path: localPathStringSchema.describe(
|
||||
path: absoluteLocalPathStringSchema.describe(
|
||||
"Absolute filesystem path to a Unity IL2CPP global-metadata.dat file",
|
||||
),
|
||||
detail: z
|
||||
@@ -95,7 +98,7 @@ export const inspectIl2cppMetadataInputSchema = z.strictObject({
|
||||
.describe(
|
||||
"summary (default) retains complete Evidence and returns identity and collection counts; complete returns every image, type, method, member, parameter and string-literal row inline",
|
||||
),
|
||||
binary_path: localPathStringSchema
|
||||
binary_path: absoluteLocalPathStringSchema
|
||||
.optional()
|
||||
.describe(
|
||||
"Absolute path to the caller-selected matching 64-bit IL2CPP binary (Mach-O GameAssembly.dylib, PE GameAssembly.dll or ELF libil2cpp.so); static reading, never executed",
|
||||
|
||||
@@ -2,7 +2,7 @@ import { z } from "zod";
|
||||
import { canBorrowParsedJsonSnapshot } from "../immutableJson.js";
|
||||
|
||||
import { pwntoolsTimeoutField } from "./nativeDeadlines.js";
|
||||
import { localPathStringSchema } from "../localPath.js";
|
||||
import { absoluteLocalPathStringSchema } from "../localPath.js";
|
||||
import { canonicalBase64Schema } from "../stringPatterns.js";
|
||||
|
||||
const unsignedHex = z.string().regex(/^0x(?:0|[1-9a-f][0-9a-f]{0,15})$/);
|
||||
@@ -19,7 +19,7 @@ const name = z.strictObject({
|
||||
|
||||
/** Explicit local object selection, independent of an active disassembler target. */
|
||||
export const inspectBinaryLayoutInputSchema = z.strictObject({
|
||||
path: localPathStringSchema.describe(
|
||||
path: absoluteLocalPathStringSchema.describe(
|
||||
"Absolute filesystem path to the selected binary",
|
||||
),
|
||||
pwntools_timeout_ms: pwntoolsTimeoutField,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { z } from "zod";
|
||||
|
||||
import { localPathStringSchema } from "../localPath.js";
|
||||
import { absoluteLocalPathStringSchema } from "../localPath.js";
|
||||
|
||||
const uint = z.number().int().min(0).max(0xffffffff);
|
||||
const range = z.strictObject({ offset: uint, bytes: uint });
|
||||
@@ -21,7 +21,7 @@ export const MAX_INSPECT_PE_DIRECTORY_ENTRIES = 65536;
|
||||
|
||||
/** Complete bounded inspection of a caller-selected PE image, without execution. */
|
||||
export const inspectPeResourcesInputSchema = z.strictObject({
|
||||
path: localPathStringSchema.describe(
|
||||
path: absoluteLocalPathStringSchema.describe(
|
||||
"Absolute filesystem path to a PE image",
|
||||
),
|
||||
max_file_bytes: z
|
||||
|
||||
@@ -2,7 +2,7 @@ import { z } from "zod";
|
||||
|
||||
import type { BinaryArchitecture } from "../binaryTargetTypes.js";
|
||||
import { pwntoolsTimeoutField } from "./nativeDeadlines.js";
|
||||
import { localPathStringSchema } from "../localPath.js";
|
||||
import { absoluteLocalPathStringSchema } from "../localPath.js";
|
||||
import { canonicalBase64Schema } from "../stringPatterns.js";
|
||||
import { recordedNoteCoverageIssues } from "./recordedCrashNoteCoverage.js";
|
||||
|
||||
@@ -141,7 +141,7 @@ const recordedCoreRegisterSets: Record<
|
||||
|
||||
/** Select an explicit recording and optionally request debugger-derived mapping context. */
|
||||
export const inspectRecordedCrashInputSchema = z.strictObject({
|
||||
path: localPathStringSchema.describe(
|
||||
path: absoluteLocalPathStringSchema.describe(
|
||||
"Absolute filesystem path to a supplied Linux ELF core",
|
||||
),
|
||||
include_debugger_context: z
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import { localPathStringSchema } from "./localPath.js";
|
||||
import { absoluteLocalPathStringSchema } from "./localPath.js";
|
||||
|
||||
/** Optional decoding applies only to explicitly selected capture records. */
|
||||
export const protobufOptionsSchema = z.strictObject({
|
||||
@@ -9,7 +9,7 @@ export const protobufOptionsSchema = z.strictObject({
|
||||
body_pointers: z.array(z.string()).optional(),
|
||||
schema: z
|
||||
.strictObject({
|
||||
descriptor_set_path: localPathStringSchema,
|
||||
descriptor_set_path: absoluteLocalPathStringSchema,
|
||||
message_type: z.string().min(1),
|
||||
})
|
||||
.optional(),
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
import { z } from "zod";
|
||||
import { digestSchema } from "../digests.js";
|
||||
import { localPathStringSchema } from "../localPath.js";
|
||||
import {
|
||||
absoluteLocalPathStringSchema,
|
||||
localPathStringSchema,
|
||||
} from "../localPath.js";
|
||||
import { BINARY_ARCHITECTURES } from "../binaryTargetTypes.js";
|
||||
|
||||
/** Preserve the format owner's architecture spelling separately from Evidence vocabulary. */
|
||||
@@ -68,7 +71,7 @@ export const rustcCommitSchema = z.strictObject({
|
||||
|
||||
/** One explicit local executable or library, independent of a disassembler target. */
|
||||
export const inspectRustBinaryInputSchema = z.strictObject({
|
||||
path: localPathStringSchema.describe(
|
||||
path: absoluteLocalPathStringSchema.describe(
|
||||
"Absolute filesystem path to the selected executable or library",
|
||||
),
|
||||
});
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
import { z } from "zod";
|
||||
import { localPathStringSchema } from "../localPath.js";
|
||||
import {
|
||||
absoluteLocalPathStringSchema,
|
||||
localPathStringSchema,
|
||||
} from "../localPath.js";
|
||||
import { windowAnnotationText } from "./annotationTextWindow.js";
|
||||
|
||||
// These strings are single sigrok arguments, never shell fragments.
|
||||
@@ -61,10 +64,16 @@ export const signalRecordingSelectorBudgetIssue = {
|
||||
message:
|
||||
"Signal selectors exceed the 32 KiB encoded argument budget; reduce decoder, format or annotation selections.",
|
||||
};
|
||||
export const decodeSignalRecordingInputSchema = signalInputShape.refine(
|
||||
const signalRecordingSelectionSchema = signalInputShape.refine(
|
||||
withinSignalRecordingSelectorBudget,
|
||||
signalRecordingSelectorBudgetIssue,
|
||||
);
|
||||
export const decodeSignalRecordingInputSchema =
|
||||
signalRecordingSelectionSchema.safeExtend({
|
||||
path: absoluteLocalPathStringSchema.describe(
|
||||
"Absolute local recording path",
|
||||
),
|
||||
});
|
||||
export const signalRecordingOptionsSchema = signalInputShape
|
||||
.omit({ path: true })
|
||||
.refine(
|
||||
@@ -82,7 +91,7 @@ export const signalRecordingSchema = z
|
||||
sha256: z.string().regex(/^[a-f0-9]{64}$/),
|
||||
bytes: z.number().int().nonnegative(),
|
||||
}),
|
||||
selection: decodeSignalRecordingInputSchema,
|
||||
selection: signalRecordingSelectionSchema,
|
||||
annotations_text: z
|
||||
.string()
|
||||
.describe(
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { z } from "zod";
|
||||
import { localPathStringSchema } from "../localPath.js";
|
||||
import { absoluteLocalPathStringSchema } from "../localPath.js";
|
||||
import type { JsonValue } from "../jsonValue.js";
|
||||
|
||||
/** Format identity is shared by discovery, catalog, and observed SQLite results. */
|
||||
@@ -28,7 +28,7 @@ const artifactSchema = z.strictObject({
|
||||
/** Inspect one frozen database and its sibling WAL, with optional ordinary-table rows. */
|
||||
export const inspectSqliteDatabaseInputSchema = z
|
||||
.strictObject({
|
||||
path: localPathStringSchema.describe(
|
||||
path: absoluteLocalPathStringSchema.describe(
|
||||
"Absolute path to an offline SQLite database snapshot",
|
||||
),
|
||||
worker_timeout_ms: workerTimeout
|
||||
|
||||
@@ -3,7 +3,7 @@ import { z } from "zod";
|
||||
import { createHash } from "node:crypto";
|
||||
import type { EvidenceSubjectTarget } from "../evidence.js";
|
||||
|
||||
import { localPathStringSchema } from "../localPath.js";
|
||||
import { absoluteLocalPathStringSchema } from "../localPath.js";
|
||||
|
||||
const digest = z.string().regex(/^[a-f0-9]{64}$/);
|
||||
const artifact = z.strictObject({
|
||||
@@ -16,11 +16,11 @@ export const DEFAULT_WASM_COMMAND_TIMEOUT_MS = 30_000;
|
||||
export const MAX_WASM_COMMAND_TIMEOUT_MS = 2_147_483_647;
|
||||
/** All artifacts are explicitly selected local files; references never trigger acquisition. */
|
||||
export const inspectWasmArtifactInputSchema = z.strictObject({
|
||||
path: localPathStringSchema.describe(
|
||||
path: absoluteLocalPathStringSchema.describe(
|
||||
"Absolute selected local WASM artifact path",
|
||||
),
|
||||
glue_paths: z.array(localPathStringSchema).max(16).default([]),
|
||||
candidate_paths: z.array(localPathStringSchema).max(16).default([]),
|
||||
glue_paths: z.array(absoluteLocalPathStringSchema).max(16).default([]),
|
||||
candidate_paths: z.array(absoluteLocalPathStringSchema).max(16).default([]),
|
||||
command_timeout_ms: z
|
||||
.number()
|
||||
.int()
|
||||
|
||||
@@ -2,7 +2,7 @@ import { z } from "zod";
|
||||
|
||||
import { jsonObjectSchema } from "./jsonValue.js";
|
||||
import { digestSchema } from "./digests.js";
|
||||
import { localPathStringSchema } from "./localPath.js";
|
||||
import { absoluteLocalPathStringSchema } from "./localPath.js";
|
||||
import {
|
||||
exportedWebScriptSchema,
|
||||
webScriptExportManifestSchema,
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
|
||||
/** Inspect one captured script's native ES module relationships. */
|
||||
export const webModuleTraceInputSchema = z.strictObject({
|
||||
manifest_path: localPathStringSchema.describe(
|
||||
manifest_path: absoluteLocalPathStringSchema.describe(
|
||||
"Absolute path to an export_web_scripts manifest",
|
||||
),
|
||||
script_index: z
|
||||
@@ -27,7 +27,7 @@ export const webModuleTraceInputSchema = z.strictObject({
|
||||
),
|
||||
import_map: z
|
||||
.strictObject({
|
||||
path: localPathStringSchema.describe(
|
||||
path: absoluteLocalPathStringSchema.describe(
|
||||
"Absolute local import-map JSON path; no fetch",
|
||||
),
|
||||
base_url: z
|
||||
|
||||
@@ -7,7 +7,7 @@ import {
|
||||
selectedBrowserDeadlineMs,
|
||||
} from "./browserDeadline.js";
|
||||
import { jsonValueSchema } from "./jsonValue.js";
|
||||
import { localPathStringSchema } from "./localPath.js";
|
||||
import { absoluteLocalPathStringSchema } from "./localPath.js";
|
||||
import {
|
||||
protobufDecodingSchema,
|
||||
protobufOptionsSchema,
|
||||
@@ -49,7 +49,7 @@ export const WEB_NETWORK_CAPTURE_FORMATS = [
|
||||
|
||||
/** Inspect an explicit retained capture without contacting its recorded destinations. */
|
||||
export const inspectWebNetworkCaptureInputSchema = z.strictObject({
|
||||
capture_path: localPathStringSchema.describe(
|
||||
capture_path: absoluteLocalPathStringSchema.describe(
|
||||
"Absolute local HAR, mitmproxy, Fiddler SAZ or aioquic qlog capture path.",
|
||||
),
|
||||
format: z.enum(WEB_NETWORK_CAPTURE_FORMATS),
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
import { z } from "zod";
|
||||
import { digestSchema } from "./digests.js";
|
||||
import { localPathStringSchema } from "./localPath.js";
|
||||
import { absoluteLocalPathStringSchema } from "./localPath.js";
|
||||
import { webScriptExportManifestSchema } from "./webScriptExport.js";
|
||||
|
||||
/** Select one source from an existing captured-script export. */
|
||||
export const selectedWebScriptInputSchema = z.strictObject({
|
||||
manifest_path: localPathStringSchema.describe(
|
||||
manifest_path: absoluteLocalPathStringSchema.describe(
|
||||
"Absolute export_web_scripts manifest path",
|
||||
),
|
||||
script_index: z
|
||||
|
||||
@@ -6,7 +6,7 @@ import {
|
||||
optionalBrowserDeadlineMs,
|
||||
} from "./browserDeadline.js";
|
||||
import { digestSchema } from "./digests.js";
|
||||
import { localPathStringSchema } from "./localPath.js";
|
||||
import { absoluteLocalPathStringSchema } from "./localPath.js";
|
||||
import { exportedWebScriptSchema } from "./webScriptExport.js";
|
||||
import {
|
||||
capturedWebManifestIdentitySchema,
|
||||
@@ -24,7 +24,7 @@ export const webSourcePositionSchema = z.strictObject({
|
||||
export const webSourceLocationInputSchema = selectedWebScriptInputSchema.extend(
|
||||
{
|
||||
source_map: z.strictObject({
|
||||
path: localPathStringSchema.describe(
|
||||
path: absoluteLocalPathStringSchema.describe(
|
||||
"Absolute local source-map path; no fetch",
|
||||
),
|
||||
url: z
|
||||
|
||||
@@ -9,10 +9,7 @@ import { toolContract } from "../contracts/toolContracts.js";
|
||||
import type { Logger } from "pino";
|
||||
import { createEvidenceToolHandler } from "./contractToolHandler.js";
|
||||
import type { WithAdmittedAnalysis } from "./analysisAdmission.js";
|
||||
import {
|
||||
toolRegistrationOptions,
|
||||
applicationValidatedInputSchema,
|
||||
} from "./toolRegistrationOptions.js";
|
||||
import { applicationToolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
|
||||
/** Bind selected-view projection to its named contract and session Evidence owner. */
|
||||
export const registerAnalysisViewTool = (
|
||||
@@ -24,7 +21,7 @@ export const registerAnalysisViewTool = (
|
||||
artifactResources?: ArtifactResourceScope,
|
||||
): void => {
|
||||
const contract = toolContract("inspect_analysis_view");
|
||||
const registration = toolRegistrationOptions(contract);
|
||||
const registration = applicationToolRegistrationOptions(contract);
|
||||
const handler = createEvidenceToolHandler(
|
||||
server,
|
||||
(input, options) =>
|
||||
@@ -38,12 +35,5 @@ export const registerAnalysisViewTool = (
|
||||
recordEvidence,
|
||||
withAdmittedAnalysis,
|
||||
);
|
||||
server.registerTool(
|
||||
contract.name,
|
||||
{
|
||||
...registration,
|
||||
inputSchema: applicationValidatedInputSchema(registration.inputSchema),
|
||||
},
|
||||
handler(contract),
|
||||
);
|
||||
server.registerTool(contract.name, registration, handler(contract));
|
||||
};
|
||||
|
||||
@@ -7,7 +7,9 @@ import type { EvidenceWriter } from "../application/investigation/InvestigationR
|
||||
import { toolContract } from "../contracts/toolContracts.js";
|
||||
import type { Logger } from "pino";
|
||||
import { logToolExecution } from "./toolLogging.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { applicationToolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { analysisInputErrorFromIssues } from "../domain/inputIssueProjection.js";
|
||||
import { err } from "../domain/result.js";
|
||||
import type { WithAdmittedAnalysis } from "./analysisAdmission.js";
|
||||
import { runAdmittedToolOperation } from "./admittedToolOperation.js";
|
||||
|
||||
@@ -23,9 +25,23 @@ export const registerBinaryDiagnosticsTools = (
|
||||
const contract = toolContract("inspect_binary_layout");
|
||||
server.registerTool(
|
||||
contract.name,
|
||||
toolRegistrationOptions(contract),
|
||||
async ({ detail, ...request }, context) =>
|
||||
runAdmittedToolOperation(
|
||||
applicationToolRegistrationOptions(contract),
|
||||
async (rawInput, context) => {
|
||||
const parsed = contract.inputSchema.safeParse(rawInput);
|
||||
if (!parsed.success)
|
||||
return server.delivery.toCallToolResult(
|
||||
err(
|
||||
analysisInputErrorFromIssues(
|
||||
contract.name,
|
||||
parsed.error.issues,
|
||||
rawInput,
|
||||
{ cause: parsed.error },
|
||||
),
|
||||
),
|
||||
contract,
|
||||
);
|
||||
const { detail, ...request } = parsed.data;
|
||||
return runAdmittedToolOperation(
|
||||
server,
|
||||
withAdmittedAnalysis,
|
||||
contract.name,
|
||||
@@ -55,6 +71,7 @@ export const registerBinaryDiagnosticsTools = (
|
||||
recorded,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
},
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,10 @@ import { toolContract } from "../contracts/toolContracts.js";
|
||||
import type { Logger } from "pino";
|
||||
import type { WithAdmittedAnalysis } from "./analysisAdmission.js";
|
||||
import { createEvidenceToolHandler } from "./contractToolHandler.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import {
|
||||
applicationToolRegistrationOptions,
|
||||
toolRegistrationOptions,
|
||||
} from "./toolRegistrationOptions.js";
|
||||
|
||||
/** Bind the named capture contract through shared admission and retained Evidence delivery. */
|
||||
export const registerCaptureTools = (
|
||||
@@ -25,7 +28,7 @@ export const registerCaptureTools = (
|
||||
);
|
||||
server.registerTool(
|
||||
contract.name,
|
||||
toolRegistrationOptions(contract),
|
||||
applicationToolRegistrationOptions(contract),
|
||||
handler(contract),
|
||||
);
|
||||
const discovery = toolContract("inspect_packet_fields");
|
||||
|
||||
@@ -6,7 +6,7 @@ import type { EvidenceWriter } from "../application/investigation/InvestigationR
|
||||
import type { WithAdmittedAnalysis } from "./analysisAdmission.js";
|
||||
import { toolContract } from "../contracts/toolContracts.js";
|
||||
import { createEvidenceToolHandler } from "./contractToolHandler.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { applicationToolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import {
|
||||
requireAnalysisViewRetention,
|
||||
summarizeRetainedAnalysis,
|
||||
@@ -49,7 +49,7 @@ export const registerDesktopApplicationTool = (
|
||||
);
|
||||
server.registerTool(
|
||||
contract.name,
|
||||
toolRegistrationOptions(contract),
|
||||
applicationToolRegistrationOptions(contract),
|
||||
handler(contract),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -30,7 +30,10 @@ 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 {
|
||||
applicationToolRegistrationOptions,
|
||||
toolRegistrationOptions,
|
||||
} from "./toolRegistrationOptions.js";
|
||||
import type { WithAdmittedAnalysis } from "./analysisAdmission.js";
|
||||
import { runAdmittedToolOperation } from "./admittedToolOperation.js";
|
||||
import { createEvidenceToolHandler } from "./contractToolHandler.js";
|
||||
@@ -160,7 +163,7 @@ const registerElectronFuseTool = (
|
||||
);
|
||||
server.registerTool(
|
||||
contract.name,
|
||||
toolRegistrationOptions(contract),
|
||||
applicationToolRegistrationOptions(contract),
|
||||
handler(contract),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -13,7 +13,7 @@ import { evmInterfaceModule } from "../composition/evm.js";
|
||||
import type { Logger } from "pino";
|
||||
import type { WithAdmittedAnalysis } from "./analysisAdmission.js";
|
||||
import { createEvidenceToolHandler } from "./contractToolHandler.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { applicationToolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
|
||||
/** Bind the named EVM contract to shared validation and session Evidence recording. */
|
||||
export const registerEvmTools = (
|
||||
@@ -55,7 +55,7 @@ export const registerEvmTools = (
|
||||
);
|
||||
server.registerTool(
|
||||
contract.name,
|
||||
toolRegistrationOptions(contract),
|
||||
applicationToolRegistrationOptions(contract),
|
||||
handler(contract),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { toolContract } from "../contracts/toolContracts.js";
|
||||
import type { Logger } from "pino";
|
||||
import type { WithAdmittedAnalysis } from "./analysisAdmission.js";
|
||||
import { createEvidenceToolHandler } from "./contractToolHandler.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { applicationToolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
|
||||
/** Bind the exact Go contract to its shared workflow and owning session Evidence ledger. */
|
||||
export const registerGoTools = (
|
||||
@@ -28,7 +28,7 @@ export const registerGoTools = (
|
||||
);
|
||||
server.registerTool(
|
||||
contract.name,
|
||||
toolRegistrationOptions(contract),
|
||||
applicationToolRegistrationOptions(contract),
|
||||
handler(contract),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { toolContract } from "../contracts/toolContracts.js";
|
||||
import type { Logger } from "pino";
|
||||
import type { WithAdmittedAnalysis } from "./analysisAdmission.js";
|
||||
import { createEvidenceToolHandler } from "./contractToolHandler.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { applicationToolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
|
||||
/** Bind the exact IL2CPP contract to its shared workflow and owning session Evidence ledger. */
|
||||
export const registerIl2cppTools = (
|
||||
@@ -28,7 +28,7 @@ export const registerIl2cppTools = (
|
||||
);
|
||||
server.registerTool(
|
||||
contract.name,
|
||||
toolRegistrationOptions(contract),
|
||||
applicationToolRegistrationOptions(contract),
|
||||
handler(contract),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -22,7 +22,7 @@ import {
|
||||
STATIC_RUNTIME_PROVIDER,
|
||||
} from "../application/InvestigationProviders.js";
|
||||
import {
|
||||
applicationValidatedInputSchema,
|
||||
applicationToolRegistrationOptions,
|
||||
toolRegistrationOptions,
|
||||
} from "./toolRegistrationOptions.js";
|
||||
import { withAdmittedAnalysis } from "./analysisAdmission.js";
|
||||
@@ -62,74 +62,62 @@ const registerChangedBehavior = (
|
||||
admission: ReturnType<typeof withAdmittedAnalysis>,
|
||||
contract: ReturnType<typeof toolContract<"find_changed_behavior">>,
|
||||
): void => {
|
||||
const registration = toolRegistrationOptions(contract);
|
||||
server.registerTool(
|
||||
contract.name,
|
||||
{
|
||||
...registration,
|
||||
inputSchema: applicationValidatedInputSchema(registration.inputSchema),
|
||||
},
|
||||
async (input, context) =>
|
||||
runAdmittedToolOperation(
|
||||
server,
|
||||
admission,
|
||||
contract.name,
|
||||
context.mcpReq.signal,
|
||||
async () => {
|
||||
const computed = await runDerivedOperation(
|
||||
context,
|
||||
contract.name,
|
||||
() => findChangedBehavior(input),
|
||||
);
|
||||
if (!computed.ok)
|
||||
return server.delivery.toCallToolResult(computed, contract);
|
||||
const { result, comparisonEvidenceIds } = computed.value;
|
||||
const closure = evidenceClosure(session, result.evidence_links);
|
||||
if (!closure.ok)
|
||||
return server.delivery.toCallToolResult(closure, contract);
|
||||
const links = closure.value;
|
||||
const evidence = createEvidence(
|
||||
undefined,
|
||||
CHANGED_BEHAVIOR_PROVIDER,
|
||||
{
|
||||
predicateType: "rea.changed-behavior",
|
||||
operation: contract.name,
|
||||
parameters: {
|
||||
comparison_evidence_ids: [...comparisonEvidenceIds],
|
||||
const registration = applicationToolRegistrationOptions(contract);
|
||||
server.registerTool(contract.name, registration, async (input, context) =>
|
||||
runAdmittedToolOperation(
|
||||
server,
|
||||
admission,
|
||||
contract.name,
|
||||
context.mcpReq.signal,
|
||||
async () => {
|
||||
const computed = await runDerivedOperation(context, contract.name, () =>
|
||||
findChangedBehavior(input),
|
||||
);
|
||||
if (!computed.ok)
|
||||
return server.delivery.toCallToolResult(computed, contract);
|
||||
const { result, comparisonEvidenceIds } = computed.value;
|
||||
const closure = evidenceClosure(session, result.evidence_links);
|
||||
if (!closure.ok)
|
||||
return server.delivery.toCallToolResult(closure, contract);
|
||||
const links = closure.value;
|
||||
const evidence = createEvidence(undefined, CHANGED_BEHAVIOR_PROVIDER, {
|
||||
predicateType: "rea.changed-behavior",
|
||||
operation: contract.name,
|
||||
parameters: {
|
||||
comparison_evidence_ids: [...comparisonEvidenceIds],
|
||||
},
|
||||
result: jsonValueSchema.parse(result),
|
||||
confidence: "derived",
|
||||
authority: "analyst-inference",
|
||||
limitations: result.limitations,
|
||||
evidenceLinks: links,
|
||||
});
|
||||
const recorded = recordWorkflowEvidence(
|
||||
session,
|
||||
evidence,
|
||||
isIncomplete(result.behavior_status),
|
||||
{
|
||||
question:
|
||||
"Did both versions behave the same under a complete controlled replay?",
|
||||
domain: "changed-behavior",
|
||||
requiredAuthority: "controlled-replay",
|
||||
requiredConfidence: "observed",
|
||||
probes: [
|
||||
{
|
||||
operation: "capture_process_scenario",
|
||||
rationale:
|
||||
"Capture both versions under the same bounded scenario and environment.",
|
||||
},
|
||||
result: jsonValueSchema.parse(result),
|
||||
confidence: "derived",
|
||||
authority: "analyst-inference",
|
||||
limitations: result.limitations,
|
||||
evidenceLinks: links,
|
||||
},
|
||||
);
|
||||
const recorded = recordWorkflowEvidence(
|
||||
session,
|
||||
evidence,
|
||||
isIncomplete(result.behavior_status),
|
||||
{
|
||||
question:
|
||||
"Did both versions behave the same under a complete controlled replay?",
|
||||
domain: "changed-behavior",
|
||||
requiredAuthority: "controlled-replay",
|
||||
requiredConfidence: "observed",
|
||||
probes: [
|
||||
{
|
||||
operation: "capture_process_scenario",
|
||||
rationale:
|
||||
"Capture both versions under the same bounded scenario and environment.",
|
||||
},
|
||||
],
|
||||
},
|
||||
);
|
||||
return server.delivery.toEvidenceToolResult(
|
||||
evidence,
|
||||
contract,
|
||||
recorded,
|
||||
);
|
||||
},
|
||||
),
|
||||
],
|
||||
},
|
||||
);
|
||||
return server.delivery.toEvidenceToolResult(
|
||||
evidence,
|
||||
contract,
|
||||
recorded,
|
||||
);
|
||||
},
|
||||
),
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -10,7 +10,7 @@ import {
|
||||
} from "./contractToolHandler.js";
|
||||
import {
|
||||
toolRegistrationOptions,
|
||||
applicationValidatedInputSchema,
|
||||
applicationToolRegistrationOptions,
|
||||
} from "./toolRegistrationOptions.js";
|
||||
import type { EvidenceLookup } from "../application/EvidenceInputResolver.js";
|
||||
import {
|
||||
@@ -43,7 +43,6 @@ export const registerPcapTools = (
|
||||
handler(clientContract),
|
||||
);
|
||||
const contract = toolContract("inspect_pcap_capture");
|
||||
const registration = toolRegistrationOptions(contract);
|
||||
const capture = createEvidenceToolHandler(
|
||||
server,
|
||||
async (rawInput, options) => {
|
||||
@@ -74,10 +73,7 @@ export const registerPcapTools = (
|
||||
);
|
||||
server.registerTool(
|
||||
contract.name,
|
||||
{
|
||||
...registration,
|
||||
inputSchema: applicationValidatedInputSchema(registration.inputSchema),
|
||||
},
|
||||
applicationToolRegistrationOptions(contract),
|
||||
capture(contract),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,7 @@ import { toolContract } from "../contracts/toolContracts.js";
|
||||
import type { Logger } from "pino";
|
||||
import { createEvidenceToolHandler } from "./contractToolHandler.js";
|
||||
import type { WithAdmittedAnalysis } from "./analysisAdmission.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { applicationToolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
|
||||
/** Register the explicit-file PE resource workflow and retain its complete Evidence. */
|
||||
export const registerPeResourcesTool = (
|
||||
@@ -25,7 +25,7 @@ export const registerPeResourcesTool = (
|
||||
);
|
||||
server.registerTool(
|
||||
contract.name,
|
||||
toolRegistrationOptions(contract),
|
||||
applicationToolRegistrationOptions(contract),
|
||||
handler(contract),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { toolContract } from "../contracts/toolContracts.js";
|
||||
import type { Logger } from "pino";
|
||||
import { createEvidenceToolHandler } from "./contractToolHandler.js";
|
||||
import type { WithAdmittedAnalysis } from "./analysisAdmission.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { applicationToolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
|
||||
/** Bind recorded crash inspection to its named contract and session Evidence owner. */
|
||||
export const registerRecordedCrashTools = (
|
||||
@@ -26,7 +26,7 @@ export const registerRecordedCrashTools = (
|
||||
);
|
||||
server.registerTool(
|
||||
contract.name,
|
||||
toolRegistrationOptions(contract),
|
||||
applicationToolRegistrationOptions(contract),
|
||||
handler(contract),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { toolContract } from "../contracts/toolContracts.js";
|
||||
import type { Logger } from "pino";
|
||||
import type { WithAdmittedAnalysis } from "./analysisAdmission.js";
|
||||
import { createEvidenceToolHandler } from "./contractToolHandler.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { applicationToolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
|
||||
/** Bind the exact Rust contract to its shared workflow and owning session Evidence ledger. */
|
||||
export const registerRustTools = (
|
||||
@@ -28,7 +28,7 @@ export const registerRustTools = (
|
||||
);
|
||||
server.registerTool(
|
||||
contract.name,
|
||||
toolRegistrationOptions(contract),
|
||||
applicationToolRegistrationOptions(contract),
|
||||
handler(contract),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,7 +5,10 @@ import { toolContract } from "../contracts/toolContracts.js";
|
||||
import type { Logger } from "pino";
|
||||
import type { WithAdmittedAnalysis } from "./analysisAdmission.js";
|
||||
import { createEvidenceToolHandler } from "./contractToolHandler.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import {
|
||||
applicationToolRegistrationOptions,
|
||||
toolRegistrationOptions,
|
||||
} from "./toolRegistrationOptions.js";
|
||||
|
||||
/** Bind the named signal contract through shared admission and retained Evidence delivery. */
|
||||
export const registerSignalTools = (
|
||||
@@ -25,7 +28,7 @@ export const registerSignalTools = (
|
||||
);
|
||||
server.registerTool(
|
||||
contract.name,
|
||||
toolRegistrationOptions(contract),
|
||||
applicationToolRegistrationOptions(contract),
|
||||
handler(contract),
|
||||
);
|
||||
const discovery = toolContract("inspect_signal_decoders");
|
||||
|
||||
@@ -5,7 +5,7 @@ import { toolContract } from "../contracts/toolContracts.js";
|
||||
import type { Logger } from "pino";
|
||||
import type { WithAdmittedAnalysis } from "./analysisAdmission.js";
|
||||
import { createEvidenceToolHandler } from "./contractToolHandler.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { applicationToolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
|
||||
/** Bind snapshot inspection to its named contract and additive session Evidence. */
|
||||
export const registerSqliteTools = (
|
||||
@@ -25,7 +25,7 @@ export const registerSqliteTools = (
|
||||
);
|
||||
server.registerTool(
|
||||
contract.name,
|
||||
toolRegistrationOptions(contract),
|
||||
applicationToolRegistrationOptions(contract),
|
||||
handler(contract),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,7 +14,10 @@ import { wasmArtifactModule } from "../composition/wasm.js";
|
||||
import type { Logger } from "pino";
|
||||
import { createEvidenceToolHandler } from "./contractToolHandler.js";
|
||||
import type { WithAdmittedAnalysis } from "./analysisAdmission.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import {
|
||||
applicationToolRegistrationOptions,
|
||||
toolRegistrationOptions,
|
||||
} from "./toolRegistrationOptions.js";
|
||||
|
||||
/** Bind the named WASM contract to shared validation and session Evidence recording. */
|
||||
export const registerWasmTools = (
|
||||
@@ -86,7 +89,7 @@ export const registerWasmTools = (
|
||||
);
|
||||
server.registerTool(
|
||||
contract.name,
|
||||
toolRegistrationOptions(contract),
|
||||
applicationToolRegistrationOptions(contract),
|
||||
handler(contract),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { toolContract } from "../contracts/toolContracts.js";
|
||||
import type { Logger } from "pino";
|
||||
import type { WithAdmittedAnalysis } from "./analysisAdmission.js";
|
||||
import { createEvidenceToolHandler } from "./contractToolHandler.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { applicationToolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
|
||||
/** Bind the module relationship workflow to its exact canonical contract. */
|
||||
export const registerWebModuleTool = (
|
||||
@@ -26,7 +26,7 @@ export const registerWebModuleTool = (
|
||||
);
|
||||
server.registerTool(
|
||||
contract.name,
|
||||
toolRegistrationOptions(contract),
|
||||
applicationToolRegistrationOptions(contract),
|
||||
handler(contract),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -14,10 +14,7 @@ import { toolContract } from "../contracts/toolContracts.js";
|
||||
import type { Logger } from "pino";
|
||||
import type { WithAdmittedAnalysis } from "./analysisAdmission.js";
|
||||
import { createEvidenceToolHandler } from "./contractToolHandler.js";
|
||||
import {
|
||||
toolRegistrationOptions,
|
||||
applicationValidatedInputSchema,
|
||||
} from "./toolRegistrationOptions.js";
|
||||
import { applicationToolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
|
||||
/** Bind historical inspection to its named contract and caller-owned Evidence writer. */
|
||||
export const registerWebNetworkCaptureTool = (
|
||||
@@ -29,11 +26,10 @@ export const registerWebNetworkCaptureTool = (
|
||||
evidenceById?: EvidenceLookup,
|
||||
): void => {
|
||||
const contract = toolContract("inspect_web_network_capture");
|
||||
const registration = toolRegistrationOptions(contract);
|
||||
const registration = applicationToolRegistrationOptions(contract);
|
||||
// Keep the exact advertised contract; the shared service validates raw input
|
||||
// before effects so caller-declared sensitivity also covers invalid arguments.
|
||||
// SDK pre-validation otherwise emits unprojected issue paths as plain text.
|
||||
const inputSchema = applicationValidatedInputSchema(registration.inputSchema);
|
||||
const handler = createEvidenceToolHandler(
|
||||
server,
|
||||
async (rawInput, options) => {
|
||||
@@ -65,9 +61,5 @@ export const registerWebNetworkCaptureTool = (
|
||||
recordEvidence,
|
||||
withAdmittedAnalysis,
|
||||
);
|
||||
server.registerTool(
|
||||
contract.name,
|
||||
{ ...registration, inputSchema },
|
||||
handler(contract),
|
||||
);
|
||||
server.registerTool(contract.name, registration, handler(contract));
|
||||
};
|
||||
|
||||
@@ -6,7 +6,7 @@ import { toolContract } from "../contracts/toolContracts.js";
|
||||
import type { Logger } from "pino";
|
||||
import type { WithAdmittedAnalysis } from "./analysisAdmission.js";
|
||||
import { createEvidenceToolHandler } from "./contractToolHandler.js";
|
||||
import { toolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
import { applicationToolRegistrationOptions } from "./toolRegistrationOptions.js";
|
||||
|
||||
/** Bind source point tracing to its exact named contract and session Evidence owner. */
|
||||
export const registerWebSourceLocationTool = (
|
||||
@@ -26,7 +26,7 @@ export const registerWebSourceLocationTool = (
|
||||
);
|
||||
server.registerTool(
|
||||
contract.name,
|
||||
toolRegistrationOptions(contract),
|
||||
applicationToolRegistrationOptions(contract),
|
||||
handler(contract),
|
||||
);
|
||||
};
|
||||
|
||||
@@ -5,17 +5,6 @@ import {
|
||||
toolOutputSchemaWithMetadata,
|
||||
} from "../contracts/toolSchemaMetadata.js";
|
||||
|
||||
/** Preserve advertised contracts while the application owns structured input failures. */
|
||||
export const applicationValidatedInputSchema = (
|
||||
schema: StandardSchemaWithJSON,
|
||||
): StandardSchemaWithJSON => ({
|
||||
"~standard": {
|
||||
...schema["~standard"],
|
||||
vendor: "rea-application",
|
||||
validate: (value: unknown) => ({ value }),
|
||||
},
|
||||
});
|
||||
|
||||
/** Project the canonical Zod contracts directly into SDK registration. */
|
||||
export const toolRegistrationOptions = <Contract extends ToolContract>(
|
||||
contract: Contract,
|
||||
@@ -32,3 +21,20 @@ export const toolRegistrationOptions = <Contract extends ToolContract>(
|
||||
outputSchema: toolOutputSchemaWithMetadata(contract),
|
||||
annotations: contract.annotations,
|
||||
});
|
||||
|
||||
/** Register a workflow that validates raw input before effects and projects its own errors. */
|
||||
export const applicationToolRegistrationOptions = <
|
||||
Contract extends ToolContract,
|
||||
>(
|
||||
contract: Contract,
|
||||
) => {
|
||||
const registration = toolRegistrationOptions(contract);
|
||||
const inputSchema: StandardSchemaWithJSON = {
|
||||
"~standard": {
|
||||
...registration.inputSchema["~standard"],
|
||||
vendor: "rea-application",
|
||||
validate: (value: unknown) => ({ value }),
|
||||
},
|
||||
};
|
||||
return { ...registration, inputSchema };
|
||||
};
|
||||
|
||||
@@ -4,6 +4,7 @@ import { resolve } from "node:path";
|
||||
import { expect, it } from "vitest";
|
||||
|
||||
import { createTestBinarySession } from "../../fixtures/binarySession.js";
|
||||
import { parseMcpToolError } from "../../fixtures/mcpToolError.js";
|
||||
import { createServer } from "../../../src/server/createServer.js";
|
||||
import { silentLogger } from "../../../src/logger.js";
|
||||
|
||||
@@ -12,6 +13,7 @@ interface PathInputCase {
|
||||
readonly field: string;
|
||||
readonly valid: Readonly<Record<string, unknown>>;
|
||||
readonly invalid: Readonly<Record<string, unknown>>;
|
||||
readonly issuePath?: readonly string[];
|
||||
}
|
||||
|
||||
const createMcp = async (
|
||||
@@ -66,6 +68,30 @@ const createMcp = async (
|
||||
}
|
||||
};
|
||||
|
||||
const standaloneAnalysisCases = (
|
||||
absolute: string,
|
||||
relative: string,
|
||||
): readonly PathInputCase[] =>
|
||||
[
|
||||
{ name: "inspect_go_binary", input: {} },
|
||||
{ name: "inspect_rust_binary", input: {} },
|
||||
{ name: "inspect_evm_interface", input: { encoding: "hex" } },
|
||||
{ name: "inspect_sqlite_database", input: {} },
|
||||
{ name: "inspect_pe_resources", input: {} },
|
||||
{ name: "inspect_electron_fuses", input: {} },
|
||||
{ name: "inspect_packet_capture", input: { fields: ["ip.addr"] } },
|
||||
{
|
||||
name: "decode_signal_recording",
|
||||
input: { decoders: "uart:rx=RX:baudrate=9600" },
|
||||
},
|
||||
].map(({ name, input }) => ({
|
||||
name,
|
||||
field: "path",
|
||||
valid: { ...input, path: absolute },
|
||||
invalid: { ...input, path: relative },
|
||||
issuePath: ["path"],
|
||||
}));
|
||||
|
||||
const inputCases = (
|
||||
absolute: string,
|
||||
relative: string,
|
||||
@@ -158,6 +184,7 @@ const inputCases = (
|
||||
],
|
||||
},
|
||||
),
|
||||
...standaloneAnalysisCases(absolute, relative),
|
||||
...["executable_path", "application_path", "application_root"].map(
|
||||
(field) =>
|
||||
pathCase(
|
||||
@@ -213,13 +240,18 @@ it("rejects relative paths through in-memory MCP handlers", async () => {
|
||||
const firmwareCalls: string[] = [];
|
||||
const { client, close } = await createMcp(firmwareCalls);
|
||||
try {
|
||||
for (const { name, field, invalid } of inputCases(
|
||||
for (const { name, field, invalid, issuePath } of inputCases(
|
||||
resolve("rea-absolute-input"),
|
||||
"relative-input",
|
||||
)) {
|
||||
const result = await client.callTool({ name, arguments: invalid });
|
||||
expect(result.isError, `${name}.${field}`).toBe(true);
|
||||
expect(JSON.stringify(result), `${name}.${field}`).toContain("absolute");
|
||||
if (issuePath !== undefined)
|
||||
expect(parseMcpToolError(result).error.details).toMatchObject({
|
||||
operation: name,
|
||||
issues: [{ path: issuePath, reason: "invalid_format" }],
|
||||
});
|
||||
}
|
||||
expect(firmwareCalls).toEqual([]);
|
||||
} finally {
|
||||
|
||||
@@ -17,12 +17,16 @@ import {
|
||||
it("advertises exact valid schemas and records inline layout without a binary target", async () => {
|
||||
const value = binaryLayoutFixture();
|
||||
let providerClosed = false;
|
||||
let providerCalls = 0;
|
||||
const service = new BinaryLayoutService({
|
||||
inspectAndClose(input, options) {
|
||||
return this.inspect(input, options);
|
||||
},
|
||||
identity: BINARY_LAYOUT_TEST_PROVIDER,
|
||||
inspect: () => Promise.resolve(ok(value)),
|
||||
inspect: () => {
|
||||
providerCalls++;
|
||||
return Promise.resolve(ok(value));
|
||||
},
|
||||
close: async () => {
|
||||
providerClosed = true;
|
||||
},
|
||||
@@ -51,6 +55,7 @@ it("advertises exact valid schemas and records inline layout without a binary ta
|
||||
);
|
||||
const { ajv, inputSchema } = schemas;
|
||||
expect(ajv.validate(inputSchema, { path: value.artifact.path })).toBe(true);
|
||||
expect(ajv.validate(inputSchema, { path: "relative.elf" })).toBe(false);
|
||||
expect(
|
||||
ajv.validate(inputSchema, {
|
||||
path: value.artifact.path,
|
||||
@@ -97,4 +102,5 @@ it("advertises exact valid schemas and records inline layout without a binary ta
|
||||
})
|
||||
).isError,
|
||||
).toBe(true);
|
||||
expect(providerCalls).toBe(1);
|
||||
});
|
||||
|
||||
@@ -15,12 +15,16 @@ import {
|
||||
it("advertises exact valid schemas and records inline recorded-core evidence without a binary target", async () => {
|
||||
const value = recordedCrashFixture();
|
||||
let providerClosed = false;
|
||||
let providerCalls = 0;
|
||||
const service = new RecordedCrashService({
|
||||
inspectAndClose(input, options) {
|
||||
return this.inspect(input, options);
|
||||
},
|
||||
identity: RECORDED_CRASH_TEST_PROVIDER,
|
||||
inspect: () => Promise.resolve(ok(value)),
|
||||
inspect: () => {
|
||||
providerCalls++;
|
||||
return Promise.resolve(ok(value));
|
||||
},
|
||||
close: async () => {
|
||||
providerClosed = true;
|
||||
},
|
||||
@@ -54,6 +58,7 @@ it("advertises exact valid schemas and records inline recorded-core evidence wit
|
||||
expect(ajv.validateSchema(inputSchema)).toBe(true);
|
||||
expect(ajv.validateSchema(outputSchema)).toBe(true);
|
||||
expect(ajv.validate(inputSchema, { path: value.artifact.path })).toBe(true);
|
||||
expect(ajv.validate(inputSchema, { path: "relative.elf" })).toBe(false);
|
||||
expect(
|
||||
ajv.validate(inputSchema, {
|
||||
path: value.artifact.path,
|
||||
@@ -85,4 +90,5 @@ it("advertises exact valid schemas and records inline recorded-core evidence wit
|
||||
})
|
||||
).isError,
|
||||
).toBe(true);
|
||||
expect(providerCalls).toBe(1);
|
||||
});
|
||||
|
||||
@@ -374,7 +374,8 @@ it("attributes rejected WASM paths to their selected fields before provider work
|
||||
paths.map((path) => ({
|
||||
path,
|
||||
reason: "invalid_format",
|
||||
message: "Expected an absolute filesystem path on this host.",
|
||||
expected: "regex",
|
||||
message: "must be an absolute local filesystem path",
|
||||
})),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -85,7 +85,12 @@ it("publishes valid SDK schemas and records the named module trace without a bin
|
||||
name: "trace_web_module_imports",
|
||||
arguments: { ...args, manifest_path: "relative.json" },
|
||||
});
|
||||
expect(invalid.isError).toBe(true);
|
||||
expect(parseMcpToolError(invalid).error).toMatchObject({
|
||||
code: "invalid_request",
|
||||
details: {
|
||||
issues: [{ path: ["manifest_path"], reason: "invalid_format" }],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("retains the module resolver cleanup owner across failed server shutdown", async () => {
|
||||
|
||||
@@ -86,7 +86,12 @@ it("publishes valid SDK schemas and records the named module trace without a bin
|
||||
name: "trace_web_source_location",
|
||||
arguments: { ...args, manifest_path: "relative.json" },
|
||||
});
|
||||
expect(invalid.isError).toBe(true);
|
||||
expect(parseMcpToolError(invalid).error).toMatchObject({
|
||||
code: "invalid_request",
|
||||
details: {
|
||||
issues: [{ path: ["manifest_path"], reason: "invalid_format" }],
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves missing Linux ps capability and recovery through source-map tracing", async () => {
|
||||
|
||||
Reference in New Issue
Block a user