311 lines
10 KiB
TypeScript
311 lines
10 KiB
TypeScript
import { Client, InMemoryTransport } from "@modelcontextprotocol/client";
|
|
import { createHash } from "node:crypto";
|
|
import { describe, expect, it } from "vitest";
|
|
import { readFile, realpath } from "node:fs/promises";
|
|
import { resolve } from "node:path";
|
|
|
|
import { BinarySession } from "../src/application/BinarySession.js";
|
|
import {
|
|
JAVASCRIPT_APPLICATION_VERSION_COMPARISON_EXAMPLE,
|
|
JAVASCRIPT_FEATURE_TRACE_EXAMPLE,
|
|
} from "../src/contracts/javascriptApplicationWorkflowExamples.js";
|
|
import { createServer } from "../src/server/createServer.js";
|
|
import { observed } from "./fixtures/analysisExecution.js";
|
|
import { PermissionAuthority } from "../src/application/PermissionAuthority.js";
|
|
import { createPermissionPolicy } from "../src/domain/permissionPolicy.js";
|
|
import { controlledReplayOutputSchema } from "../src/domain/javascriptReplay.js";
|
|
import { nodeCharacterizationPreparationOutputSchema } from "../src/domain/nodeRuntimeCharacterization.js";
|
|
|
|
describe("application workflow MCP parity", () => {
|
|
it("traces and compares authenticated graph Evidence in the session", async () => {
|
|
const session = new BinarySession(() => ({
|
|
execute: () => Promise.resolve(observed(null)),
|
|
close: () => Promise.resolve(),
|
|
}));
|
|
const server = createServer(session, session);
|
|
const client = new Client({
|
|
name: "application-workflow-test",
|
|
version: "1",
|
|
});
|
|
const [clientTransport, serverTransport] =
|
|
InMemoryTransport.createLinkedPair();
|
|
try {
|
|
await server.connect(serverTransport);
|
|
await client.connect(clientTransport);
|
|
|
|
const traced = await client.callTool({
|
|
name: "trace_application_feature",
|
|
arguments: JAVASCRIPT_FEATURE_TRACE_EXAMPLE,
|
|
});
|
|
expect(traced.isError).not.toBe(true);
|
|
expect(traced.structuredContent).toMatchObject({
|
|
result: {
|
|
schema_version: 1,
|
|
coverage: { status: expect.any(String) },
|
|
},
|
|
});
|
|
|
|
const compared = await client.callTool({
|
|
name: "compare_application_versions",
|
|
arguments: {
|
|
...JAVASCRIPT_APPLICATION_VERSION_COMPARISON_EXAMPLE,
|
|
unknown_registry_approved: true,
|
|
},
|
|
});
|
|
expect(compared.isError).not.toBe(true);
|
|
expect(compared.structuredContent).toMatchObject({
|
|
result: {
|
|
schema_version: 1,
|
|
summary: { unknown: expect.any(Number) },
|
|
coverage: { status: expect.any(String) },
|
|
},
|
|
});
|
|
expect(session.exportEvidenceBundle().records.length).toBeGreaterThan(2);
|
|
|
|
const spoofed = await client.callTool({
|
|
name: "trace_application_feature",
|
|
arguments: {
|
|
...JAVASCRIPT_FEATURE_TRACE_EXAMPLE,
|
|
application: {
|
|
...JAVASCRIPT_FEATURE_TRACE_EXAMPLE.application,
|
|
provider: { id: "spoofed", name: "spoofed", version: "1" },
|
|
},
|
|
},
|
|
});
|
|
expect(spoofed.isError).toBe(true);
|
|
} finally {
|
|
await client.close();
|
|
await server.close();
|
|
await session.close();
|
|
}
|
|
});
|
|
|
|
it("plans and executes the same controlled replay contract", async () => {
|
|
const root = resolve("tests/fixtures/replay");
|
|
const policy = {
|
|
enabled: true,
|
|
roots: [root],
|
|
nodePath: process.execPath,
|
|
bubblewrapPath: process.execPath,
|
|
systemdRunPath: process.execPath,
|
|
systemctlPath: process.execPath,
|
|
shellPath: process.execPath,
|
|
};
|
|
const ceiling = {
|
|
capability: "javascript_replay" as const,
|
|
roots: [root],
|
|
executables: [
|
|
policy.nodePath,
|
|
policy.bubblewrapPath,
|
|
policy.systemdRunPath,
|
|
policy.systemctlPath,
|
|
policy.shellPath,
|
|
],
|
|
environment_names: [],
|
|
network: "none" as const,
|
|
mount: true,
|
|
};
|
|
const authority = new PermissionAuthority(
|
|
createPermissionPolicy(
|
|
[ceiling],
|
|
[
|
|
{
|
|
...ceiling,
|
|
grant_id: "administrator:javascript_replay",
|
|
lifetime: "administrator",
|
|
operation_identity: null,
|
|
expires_at: null,
|
|
},
|
|
],
|
|
),
|
|
);
|
|
const session = new BinarySession(() => ({
|
|
execute: () => Promise.resolve(observed(null)),
|
|
close: () => Promise.resolve(),
|
|
}));
|
|
const server = createServer(session, session, {
|
|
permissionAuthority: authority,
|
|
javascriptReplayPolicy: policy,
|
|
javascriptReplayHost: {
|
|
readSource: async (path) => ({
|
|
canonicalPath: await realpath(path),
|
|
bytes: await readFile(path),
|
|
}),
|
|
identifyExecutable: async (path) => ({
|
|
path,
|
|
version: "fixture",
|
|
sha256: "1".repeat(64),
|
|
}),
|
|
identifyWorker: async () => ({
|
|
path: "/fixture/worker.js",
|
|
version: "fixture-worker-1",
|
|
sha256: "2".repeat(64),
|
|
}),
|
|
identifyRuntimeClosure: async (path) => [
|
|
{
|
|
sourcePath: path,
|
|
destinationPath: "/runtime/node",
|
|
sha256: "1".repeat(64),
|
|
},
|
|
],
|
|
seccompDigest: () => "3".repeat(64),
|
|
probe: async () => undefined,
|
|
},
|
|
javascriptReplayRunner: {
|
|
execute: async (prepared) => ({
|
|
schema_version: 1,
|
|
plan_digest: prepared.publicPlan.plan_digest,
|
|
outcomes: [
|
|
{
|
|
case_id: "heading",
|
|
outcome: "return",
|
|
value: { type: "heading", text: "Title" },
|
|
input_sha256:
|
|
prepared.publicPlan.cases[0]?.sha256 ?? "0".repeat(64),
|
|
output_sha256: "2".repeat(64),
|
|
truncated: false,
|
|
},
|
|
],
|
|
stderr: "",
|
|
termination: "completed",
|
|
cleanup: { state: "complete", residual_resources: [] },
|
|
limitations: ["fixture runner"],
|
|
reproducer: null,
|
|
}),
|
|
},
|
|
});
|
|
const client = new Client({ name: "replay-parity-test", version: "1" });
|
|
const [clientTransport, serverTransport] =
|
|
InMemoryTransport.createLinkedPair();
|
|
const input = {
|
|
mode: "plan",
|
|
left: {
|
|
modules: [
|
|
{
|
|
alias: "parser",
|
|
path: resolve(root, "parser.mjs"),
|
|
format: "esm",
|
|
role: "module",
|
|
dependencies: {},
|
|
},
|
|
],
|
|
entry_alias: "parser",
|
|
entry_export: "default",
|
|
},
|
|
cases: [{ case_id: "heading", arguments: ["# Title"] }],
|
|
approved: false,
|
|
};
|
|
try {
|
|
await server.connect(serverTransport);
|
|
await client.connect(clientTransport);
|
|
const planned = await client.callTool({
|
|
name: "run_controlled_replay",
|
|
arguments: input,
|
|
});
|
|
expect(planned.isError).not.toBe(true);
|
|
const digest = controlledReplayOutputSchema.parse(
|
|
planned.structuredContent,
|
|
).plan?.plan_digest;
|
|
expect(digest).toMatch(/^[a-f0-9]{64}$/u);
|
|
const executed = await client.callTool({
|
|
name: "run_controlled_replay",
|
|
arguments: {
|
|
...input,
|
|
mode: "execute",
|
|
approved: true,
|
|
plan_digest: digest,
|
|
},
|
|
});
|
|
expect(executed.isError).not.toBe(true);
|
|
expect(executed.structuredContent).toMatchObject({
|
|
phase: "execute",
|
|
evidence: {
|
|
authority: "controlled-replay",
|
|
provider: { id: "rea-javascript-replay" },
|
|
},
|
|
});
|
|
expect(session.exportEvidenceBundle().records).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({ authority: "controlled-replay" }),
|
|
]),
|
|
);
|
|
const factoryPath = resolve(root, "sanitizer.factory.txt");
|
|
const factoryBytes = await readFile(factoryPath);
|
|
const factorySha256 = createHash("sha256")
|
|
.update(factoryBytes)
|
|
.digest("hex");
|
|
const characterizationInput = {
|
|
preparation_approved: true,
|
|
selected_alias: "bundle",
|
|
expected_effect: "pure",
|
|
instrumentation: {
|
|
artifact_path: factoryPath,
|
|
artifact_sha256: factorySha256,
|
|
selection: {
|
|
byte_start: 0,
|
|
byte_end: factoryBytes.byteLength,
|
|
selected_sha256: factorySha256,
|
|
export_name: "selected",
|
|
},
|
|
},
|
|
replay: {
|
|
mode: "plan",
|
|
left: {
|
|
modules: [
|
|
{
|
|
alias: "bundle",
|
|
path: factoryPath,
|
|
format: "commonjs-factory",
|
|
role: "module",
|
|
dependencies: {},
|
|
},
|
|
],
|
|
entry_alias: "bundle",
|
|
entry_export: "selected",
|
|
},
|
|
cases: [{ case_id: "heading", arguments: ["# Title"] }],
|
|
approved: false,
|
|
},
|
|
};
|
|
const characterizationPlan = await client.callTool({
|
|
name: "prepare_node_characterization",
|
|
arguments: characterizationInput,
|
|
});
|
|
expect(characterizationPlan.isError).not.toBe(true);
|
|
const approvedPlan = nodeCharacterizationPreparationOutputSchema.parse(
|
|
characterizationPlan.structuredContent,
|
|
).plan;
|
|
const characterized = await client.callTool({
|
|
name: "execute_node_characterization",
|
|
arguments: {
|
|
execution_approved: true,
|
|
approved_plan_sha256: approvedPlan.plan_sha256,
|
|
preparation: characterizationInput,
|
|
},
|
|
});
|
|
expect(characterized.isError).not.toBe(true);
|
|
expect(characterized.structuredContent).toMatchObject({
|
|
phase: "execution",
|
|
evidence: {
|
|
authority: "controlled-replay",
|
|
provider: { id: "rea-node-characterization" },
|
|
},
|
|
});
|
|
expect(session.exportEvidenceBundle().records).toEqual(
|
|
expect.arrayContaining([
|
|
expect.objectContaining({
|
|
predicate_type: "rea.javascript-export-transformation/v1",
|
|
}),
|
|
expect.objectContaining({
|
|
predicate_type: "rea.runtime-characterization/v1",
|
|
}),
|
|
]),
|
|
);
|
|
} finally {
|
|
await client.close();
|
|
await server.close();
|
|
await session.close();
|
|
}
|
|
});
|
|
});
|