import { describe, expect, it } from "@effect/vitest";import { Data, Effect, Option, Schema } from "effect"; import { ElicitationResponse, createExecutor, definePlugin } from "@executor-js/sdk";import { makeTestConfig } from "@executor-js/sdk/testing";import { ExecutionToolError } from "./errors";import { makeExecutorToolInvoker } from "./tool-invoker"; const EmptyInputSchema = Schema.toStandardSchemaV1( Schema.toStandardJSONSchemaV1(Schema.Struct({})),); const acceptAll = () => Effect.succeed(ElicitationResponse.make({ action: "accept" })); // Plugin-internal tagged error whose `cause` carries internal diagnostics.// The dispatcher must route this through the opaque-generic path so none of// that context reaches the sandbox via Error.message.class FakePluginInvocationError extends Data.TaggedError("PluginInvocationError")<{ readonly message: string; readonly cause: unknown;}> {} // Mirrors the openapi plugin's OpenApiInvocationError shape (the dispatcher// matches it structurally by tag, not by class identity, since this package// does not depend on the plugin). Pre-flight raises carry statusCode: None// and no cause and must surface verbatim; transport/post-response failures// must stay opaque.class FakeOpenApiInvocationError extends Data.TaggedError("OpenApiInvocationError")<{ readonly message: string; readonly statusCode: Option.Option<number>; readonly cause?: unknown;}> {} const leakyPlugin = definePlugin(() => ({ id: "leaky-test" as const, storage: () => ({}), staticIntegrations: () => [ { id: "leaky", kind: "in-memory", name: "Leaky", tools: [ { name: "failsWithCause", description: "", inputSchema: EmptyInputSchema, handler: () => Effect.fail( new FakePluginInvocationError({ message: "Upstream request failed", cause: { _tag: "InternalTransportError", request: { method: "GET", url: "https://internal.service.local/v1/resources?trace=trace-123", headers: { "x-internal-routing": "private-cluster" }, }, stack: "Error: connect failed\n at plugin-transport.ts:42:11", note: "internal diagnostic detail", }, }), ), }, { name: "throwsRawError", description: "", inputSchema: EmptyInputSchema, handler: () => Effect.fail( Object.assign( // oxlint-disable-next-line executor/no-error-constructor -- boundary: leak test deliberately fails with a raw Error + crafted stack to assert the dispatcher's opaque-generic redaction new Error("Internal: secret 'sk_live_abcd' rotation failed"), { stack: "Error: Internal: secret 'sk_live_abcd' rotation failed\n at /home/svc/.../secret-store.ts:88", }, ), ), }, { name: "failsPreflightPathParam", description: "", inputSchema: EmptyInputSchema, handler: () => Effect.fail( new FakeOpenApiInvocationError({ message: "Missing required path parameter: datasourceId", statusCode: Option.none(), }), ), }, { name: "failsPreflightBody", description: "", inputSchema: EmptyInputSchema, handler: () => Effect.fail( new FakeOpenApiInvocationError({ message: "Missing required request body", statusCode: Option.none(), }), ), }, { name: "failsOpenApiTransport", description: "", inputSchema: EmptyInputSchema, handler: () => Effect.fail( new FakeOpenApiInvocationError({ message: "HTTP request failed", statusCode: Option.none(), cause: { _tag: "RequestError", request: { url: "https://internal.service.local/v1?token=tok-456" }, }, }), ), }, { name: "failsOpenApiPostResponse", description: "", inputSchema: EmptyInputSchema, handler: () => Effect.fail( new FakeOpenApiInvocationError({ message: "Failed to read response body", statusCode: Option.some(502), cause: { _tag: "ResponseError", note: "internal parser detail" }, }), ), }, ], }, ],})); describe("internal-error leak audit (opaque defects)", () => { it.effect("plugin tagged error: defect surfaces only as opaque generic + correlation id", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [leakyPlugin()] as const })); const invoker = makeExecutorToolInvoker(executor, { invokeOptions: { onElicitation: acceptAll }, }); const err = yield* Effect.flip(invoker.invoke({ path: "leaky.failsWithCause", args: {} })); expect(err).toBeInstanceOf(ExecutionToolError); // oxlint-disable-next-line executor/no-unknown-error-message -- boundary: leak test inspects the rendered message to assert it is the opaque generic const msg = (err as { message: string }).message; // Must be the canonical opaque shape: "Internal tool error [<hex>]" expect(msg).toMatch(/^Internal tool error \[[0-9a-f]{8}\]$/); // Crucially, no internal context leaks expect(msg).not.toContain("trace-123"); expect(msg).not.toContain("private-cluster"); expect(msg).not.toContain("internal.service.local"); expect(msg).not.toContain("InternalTransportError"); expect(msg).not.toContain("Upstream request failed"); }), ); it.effect("plain Error with stack: stack and message do NOT escape", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [leakyPlugin()] as const })); const invoker = makeExecutorToolInvoker(executor, { invokeOptions: { onElicitation: acceptAll }, }); const err = yield* Effect.flip(invoker.invoke({ path: "leaky.throwsRawError", args: {} })); // oxlint-disable-next-line executor/no-unknown-error-message -- boundary: leak test inspects the rendered message to assert it is the opaque generic const msg = (err as { message: string }).message; expect(msg).toMatch(/^Internal tool error \[[0-9a-f]{8}\]$/); expect(msg).not.toContain("secret-store.ts"); expect(msg).not.toContain("at /home/"); expect(msg).not.toContain("sk_live_abcd"); }), );}); describe("openapi pre-flight invocation errors (expected failures)", () => { it.effect("missing path parameter surfaces verbatim as invalid_tool_arguments", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [leakyPlugin()] as const })); const invoker = makeExecutorToolInvoker(executor, { invokeOptions: { onElicitation: acceptAll }, }); const result = yield* invoker.invoke({ path: "leaky.failsPreflightPathParam", args: {} }); expect(result).toEqual({ ok: false, error: { code: "invalid_tool_arguments", message: "Missing required path parameter: datasourceId", }, }); }), ); it.effect("missing request body surfaces verbatim as invalid_tool_arguments", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [leakyPlugin()] as const })); const invoker = makeExecutorToolInvoker(executor, { invokeOptions: { onElicitation: acceptAll }, }); const result = yield* invoker.invoke({ path: "leaky.failsPreflightBody", args: {} }); expect(result).toEqual({ ok: false, error: { code: "invalid_tool_arguments", message: "Missing required request body", }, }); }), ); it.effect("transport failure (statusCode None but cause present) stays opaque", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [leakyPlugin()] as const })); const invoker = makeExecutorToolInvoker(executor, { invokeOptions: { onElicitation: acceptAll }, }); const err = yield* Effect.flip( invoker.invoke({ path: "leaky.failsOpenApiTransport", args: {} }), ); expect(err).toBeInstanceOf(ExecutionToolError); // oxlint-disable-next-line executor/no-unknown-error-message -- boundary: leak test inspects the rendered message to assert it is the opaque generic const msg = (err as { message: string }).message; expect(msg).toMatch(/^Internal tool error \[[0-9a-f]{8}\]$/); expect(msg).not.toContain("internal.service.local"); expect(msg).not.toContain("tok-456"); expect(msg).not.toContain("HTTP request failed"); }), ); it.effect("post-response failure (statusCode Some) stays opaque", () => Effect.gen(function* () { const executor = yield* createExecutor(makeTestConfig({ plugins: [leakyPlugin()] as const })); const invoker = makeExecutorToolInvoker(executor, { invokeOptions: { onElicitation: acceptAll }, }); const err = yield* Effect.flip( invoker.invoke({ path: "leaky.failsOpenApiPostResponse", args: {} }), ); expect(err).toBeInstanceOf(ExecutionToolError); // oxlint-disable-next-line executor/no-unknown-error-message -- boundary: leak test inspects the rendered message to assert it is the opaque generic const msg = (err as { message: string }).message; expect(msg).toMatch(/^Internal tool error \[[0-9a-f]{8}\]$/); expect(msg).not.toContain("Failed to read response body"); expect(msg).not.toContain("internal parser detail"); }), );});
import { describe, expect, it } from "@effect/vitest";
import { Data, Effect, Option, Schema } from "effect";
import { ElicitationResponse, createExecutor, definePlugin } from "@executor-js/sdk";
import { makeTestConfig } from "@executor-js/sdk/testing";
import { ExecutionToolError } from "./errors";
import { makeExecutorToolInvoker } from "./tool-invoker";
const EmptyInputSchema = Schema.toStandardSchemaV1(
Schema.toStandardJSONSchemaV1(Schema.Struct({})),
);
const acceptAll = () => Effect.succeed(ElicitationResponse.make({ action: "accept" }));
// Plugin-internal tagged error whose `cause` carries internal diagnostics.
// The dispatcher must route this through the opaque-generic path so none of
// that context reaches the sandbox via Error.message.
class FakePluginInvocationError extends Data.TaggedError("PluginInvocationError")<{
readonly message: string;
readonly cause: unknown;
}> {}
// Mirrors the openapi plugin's OpenApiInvocationError shape (the dispatcher
// matches it structurally by tag, not by class identity, since this package
// does not depend on the plugin). Pre-flight raises carry statusCode: None
// and no cause and must surface verbatim; transport/post-response failures
// must stay opaque.
class FakeOpenApiInvocationError extends Data.TaggedError("OpenApiInvocationError")<{
readonly message: string;
readonly statusCode: Option.Option<number>;
readonly cause?: unknown;
}> {}
const leakyPlugin = definePlugin(() => ({
id: "leaky-test" as const,
storage: () => ({}),
staticIntegrations: () => [
{
id: "leaky",
kind: "in-memory",
name: "Leaky",
tools: [
{
name: "failsWithCause",
description: "",
inputSchema: EmptyInputSchema,
handler: () =>
Effect.fail(
new FakePluginInvocationError({
message: "Upstream request failed",
cause: {
_tag: "InternalTransportError",
request: {
method: "GET",
url: "https://internal.service.local/v1/resources?trace=trace-123",
headers: { "x-internal-routing": "private-cluster" },
},
stack: "Error: connect failed\n at plugin-transport.ts:42:11",
note: "internal diagnostic detail",
},
}),
),
},
{
name: "throwsRawError",
description: "",
inputSchema: EmptyInputSchema,
handler: () =>
Effect.fail(
Object.assign(
// oxlint-disable-next-line executor/no-error-constructor -- boundary: leak test deliberately fails with a raw Error + crafted stack to assert the dispatcher's opaque-generic redaction
new Error("Internal: secret 'sk_live_abcd' rotation failed"),
{
stack:
"Error: Internal: secret 'sk_live_abcd' rotation failed\n at /home/svc/.../secret-store.ts:88",
},
),
),
},
{
name: "failsPreflightPathParam",
description: "",
inputSchema: EmptyInputSchema,
handler: () =>
Effect.fail(
new FakeOpenApiInvocationError({
message: "Missing required path parameter: datasourceId",
statusCode: Option.none(),
}),
),
},
{
name: "failsPreflightBody",
description: "",
inputSchema: EmptyInputSchema,
handler: () =>
Effect.fail(
new FakeOpenApiInvocationError({
message: "Missing required request body",
statusCode: Option.none(),
}),
),
},
{
name: "failsOpenApiTransport",
description: "",
inputSchema: EmptyInputSchema,
handler: () =>
Effect.fail(
new FakeOpenApiInvocationError({
message: "HTTP request failed",
statusCode: Option.none(),
cause: {
_tag: "RequestError",
request: { url: "https://internal.service.local/v1?token=tok-456" },
},
}),
),
},
{
name: "failsOpenApiPostResponse",
description: "",
inputSchema: EmptyInputSchema,
handler: () =>
Effect.fail(
new FakeOpenApiInvocationError({
message: "Failed to read response body",
statusCode: Option.some(502),
cause: { _tag: "ResponseError", note: "internal parser detail" },
}),
),
},
],
},
],
}));
describe("internal-error leak audit (opaque defects)", () => {
it.effect("plugin tagged error: defect surfaces only as opaque generic + correlation id", () =>
Effect.gen(function* () {
const executor = yield* createExecutor(makeTestConfig({ plugins: [leakyPlugin()] as const }));
const invoker = makeExecutorToolInvoker(executor, {
invokeOptions: { onElicitation: acceptAll },
});
const err = yield* Effect.flip(invoker.invoke({ path: "leaky.failsWithCause", args: {} }));
expect(err).toBeInstanceOf(ExecutionToolError);
// oxlint-disable-next-line executor/no-unknown-error-message -- boundary: leak test inspects the rendered message to assert it is the opaque generic
const msg = (err as { message: string }).message;
// Must be the canonical opaque shape: "Internal tool error [<hex>]"
expect(msg).toMatch(/^Internal tool error \[[0-9a-f]{8}\]$/);
// Crucially, no internal context leaks
expect(msg).not.toContain("trace-123");
expect(msg).not.toContain("private-cluster");
expect(msg).not.toContain("internal.service.local");
expect(msg).not.toContain("InternalTransportError");
expect(msg).not.toContain("Upstream request failed");
}),
);
it.effect("plain Error with stack: stack and message do NOT escape", () =>
Effect.gen(function* () {
const executor = yield* createExecutor(makeTestConfig({ plugins: [leakyPlugin()] as const }));
const invoker = makeExecutorToolInvoker(executor, {
invokeOptions: { onElicitation: acceptAll },
});
const err = yield* Effect.flip(invoker.invoke({ path: "leaky.throwsRawError", args: {} }));
// oxlint-disable-next-line executor/no-unknown-error-message -- boundary: leak test inspects the rendered message to assert it is the opaque generic
const msg = (err as { message: string }).message;
expect(msg).toMatch(/^Internal tool error \[[0-9a-f]{8}\]$/);
expect(msg).not.toContain("secret-store.ts");
expect(msg).not.toContain("at /home/");
expect(msg).not.toContain("sk_live_abcd");
}),
);
});
describe("openapi pre-flight invocation errors (expected failures)", () => {
it.effect("missing path parameter surfaces verbatim as invalid_tool_arguments", () =>
Effect.gen(function* () {
const executor = yield* createExecutor(makeTestConfig({ plugins: [leakyPlugin()] as const }));
const invoker = makeExecutorToolInvoker(executor, {
invokeOptions: { onElicitation: acceptAll },
});
const result = yield* invoker.invoke({ path: "leaky.failsPreflightPathParam", args: {} });
expect(result).toEqual({
ok: false,
error: {
code: "invalid_tool_arguments",
message: "Missing required path parameter: datasourceId",
},
});
}),
);
it.effect("missing request body surfaces verbatim as invalid_tool_arguments", () =>
Effect.gen(function* () {
const executor = yield* createExecutor(makeTestConfig({ plugins: [leakyPlugin()] as const }));
const invoker = makeExecutorToolInvoker(executor, {
invokeOptions: { onElicitation: acceptAll },
});
const result = yield* invoker.invoke({ path: "leaky.failsPreflightBody", args: {} });
expect(result).toEqual({
ok: false,
error: {
code: "invalid_tool_arguments",
message: "Missing required request body",
},
});
}),
);
it.effect("transport failure (statusCode None but cause present) stays opaque", () =>
Effect.gen(function* () {
const executor = yield* createExecutor(makeTestConfig({ plugins: [leakyPlugin()] as const }));
const invoker = makeExecutorToolInvoker(executor, {
invokeOptions: { onElicitation: acceptAll },
});
const err = yield* Effect.flip(
invoker.invoke({ path: "leaky.failsOpenApiTransport", args: {} }),
);
expect(err).toBeInstanceOf(ExecutionToolError);
// oxlint-disable-next-line executor/no-unknown-error-message -- boundary: leak test inspects the rendered message to assert it is the opaque generic
const msg = (err as { message: string }).message;
expect(msg).toMatch(/^Internal tool error \[[0-9a-f]{8}\]$/);
expect(msg).not.toContain("internal.service.local");
expect(msg).not.toContain("tok-456");
expect(msg).not.toContain("HTTP request failed");
}),
);
it.effect("post-response failure (statusCode Some) stays opaque", () =>
Effect.gen(function* () {
const executor = yield* createExecutor(makeTestConfig({ plugins: [leakyPlugin()] as const }));
const invoker = makeExecutorToolInvoker(executor, {
invokeOptions: { onElicitation: acceptAll },
});
const err = yield* Effect.flip(
invoker.invoke({ path: "leaky.failsOpenApiPostResponse", args: {} }),
);
expect(err).toBeInstanceOf(ExecutionToolError);
// oxlint-disable-next-line executor/no-unknown-error-message -- boundary: leak test inspects the rendered message to assert it is the opaque generic
const msg = (err as { message: string }).message;
expect(msg).toMatch(/^Internal tool error \[[0-9a-f]{8}\]$/);
expect(msg).not.toContain("Failed to read response body");
expect(msg).not.toContain("internal parser detail");
}),
);
});