Fresh history cut from cynarAI/evoo antigravity-handoff/project-timeline (Voice First + project timeline). Brand docs/titles as evOo; keep @rakazo/* packages as tech debt. Excludes node_modules, caches, secrets, voice blobs.
661 lines
24 KiB
TypeScript
661 lines
24 KiB
TypeScript
import { strict as assert } from "node:assert";
|
|
import { createServer } from "node:http";
|
|
import type { AddressInfo } from "node:net";
|
|
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
import { StoredMcpOAuthProvider } from "./mcp-oauth.js";
|
|
import {
|
|
McpSession,
|
|
secureFetch,
|
|
validateUrl,
|
|
withEndpointOriginFallback,
|
|
} from "./mcp-transport.js";
|
|
|
|
afterEach(() => vi.unstubAllGlobals());
|
|
|
|
const TEST_NETWORK = {
|
|
// Read the global per call so a fetch stubbed after construction still wins.
|
|
fetch: (input: string | URL | Request, init?: RequestInit) => globalThis.fetch(input, init),
|
|
resolveHostname: async () => [{ address: "203.0.113.10", family: 4 }],
|
|
};
|
|
|
|
function logicalHref(input: string | URL | Request, init?: RequestInit): string {
|
|
const url = new URL(
|
|
typeof input === "string" || input instanceof URL ? String(input) : input.url,
|
|
);
|
|
const host = new Headers(input instanceof Request ? input.headers : init?.headers).get("host");
|
|
if (host) url.host = host;
|
|
return url.href;
|
|
}
|
|
|
|
describe("MCP transport seam", () => {
|
|
it("rejects unsafe URLs and oversized URLs before network access", () => {
|
|
expect(() => validateUrl("http://remote.example/mcp")).toThrow("HTTPS");
|
|
expect(() => validateUrl("https://user:pass@example.com/mcp")).toThrow("credentials");
|
|
expect(() => validateUrl(`https://example.com/${"x".repeat(2_100)}`)).toThrow("exceeds");
|
|
expect(() => validateUrl("http://127.0.0.1:1234/mcp")).toThrow("HTTPS");
|
|
expect(validateUrl("http://127.0.0.1:1234/mcp", { allowHttpLocalhost: true }).hostname).toBe(
|
|
"127.0.0.1",
|
|
);
|
|
});
|
|
|
|
it("gates stdio by exact command allowlist", async () => {
|
|
const session = new McpSession();
|
|
await expect(
|
|
session.connectStdio({
|
|
command: process.execPath,
|
|
allowedCommands: ["/definitely-not-node"],
|
|
}),
|
|
).rejects.toThrow("allowlist");
|
|
await session.close();
|
|
assert.ok(true);
|
|
});
|
|
|
|
it("rejects remote endpoints that resolve to a private address", async () => {
|
|
const fetchImpl = vi.fn();
|
|
const session = new McpSession();
|
|
await expect(
|
|
session.connectRemote({
|
|
url: "https://metadata.example.test/mcp",
|
|
fallbackToSse: false,
|
|
network: {
|
|
fetch: fetchImpl,
|
|
resolveHostname: async () => [{ address: "169.254.169.254", family: 4 }],
|
|
},
|
|
}),
|
|
).rejects.toThrow("private address");
|
|
expect(fetchImpl).not.toHaveBeenCalled();
|
|
await session.close();
|
|
});
|
|
|
|
it("requires a connected session for operations", async () => {
|
|
const session = new McpSession();
|
|
await expect(session.listTools()).rejects.toThrow("not connected");
|
|
await expect(session.callTool("echo")).rejects.toThrow("not connected");
|
|
});
|
|
|
|
it("preserves SDK session and protocol headers after stateful initialization", async () => {
|
|
const requests: Request[] = [];
|
|
const session = new McpSession();
|
|
try {
|
|
await session.connectRemote({
|
|
url: "https://mcp.example.test/mcp",
|
|
network: {
|
|
...TEST_NETWORK,
|
|
fetch: vi.fn(async (input: string | URL | Request, init?: RequestInit) => {
|
|
const request = new Request(input, init);
|
|
requests.push(request);
|
|
const message = request.method === "POST" ? await request.json() : undefined;
|
|
if (message?.method === "initialize") {
|
|
return Response.json(
|
|
{
|
|
jsonrpc: "2.0",
|
|
id: message.id,
|
|
result: {
|
|
protocolVersion: "2025-11-25",
|
|
capabilities: { tools: {} },
|
|
serverInfo: { name: "fake-stateful-server", version: "1" },
|
|
},
|
|
},
|
|
{ headers: { "Mcp-Session-Id": "fake-session" } },
|
|
);
|
|
}
|
|
if (
|
|
request.headers.get("mcp-session-id") !== "fake-session" ||
|
|
request.headers.get("mcp-protocol-version") !== "2025-11-25"
|
|
) {
|
|
return new Response(null, { status: 400 });
|
|
}
|
|
if (request.method === "GET") return new Response(null, { status: 405 });
|
|
if (message?.method === "tools/list") {
|
|
return Response.json({ jsonrpc: "2.0", id: message.id, result: { tools: [] } });
|
|
}
|
|
return new Response(null, { status: 202 });
|
|
}),
|
|
},
|
|
});
|
|
|
|
await expect(session.listTools()).resolves.toEqual({ tools: [] });
|
|
expect(requests[0]?.headers.has("mcp-session-id")).toBe(false);
|
|
expect(requests.length).toBeGreaterThanOrEqual(3);
|
|
expect(
|
|
requests
|
|
.slice(1)
|
|
.every((request) => request.headers.get("mcp-session-id") === "fake-session"),
|
|
).toBe(true);
|
|
} finally {
|
|
await session.close();
|
|
}
|
|
});
|
|
|
|
it.each([
|
|
["https://mcp.example.test/mcp", "http://localhost:8123"],
|
|
["https://mcp.example.test/mcp", "http://127.0.0.1:8123"],
|
|
["https://mcp.example.test/mcp", "http://[::1]:8123"],
|
|
["https://localhost:8123/mcp", "http://localhost:8123"],
|
|
["http://localhost:8123/mcp", "http://localhost:8124"],
|
|
["http://localhost:8123/mcp", "http://127.0.0.1:8123"],
|
|
["http://[::1]:8123/mcp", "http://[::1]:8124"],
|
|
])("does not extend the local exception from %s to %s", async (resource, origin) => {
|
|
const fetchImpl = vi.fn();
|
|
const safeFetch = secureFetch(
|
|
new URL(resource),
|
|
{ allowHttpLocalhost: true, allowLocalHttpCredentials: true },
|
|
{},
|
|
{ ...TEST_NETWORK, fetch: fetchImpl },
|
|
);
|
|
try {
|
|
await expect(safeFetch(`${origin}/.well-known/oauth-protected-resource`)).rejects.toThrow(
|
|
"HTTPS",
|
|
);
|
|
await expect(
|
|
safeFetch(`${origin}/token`, { method: "POST", body: "fake-token-body" }),
|
|
).rejects.toThrow("HTTPS");
|
|
expect(fetchImpl).not.toHaveBeenCalled();
|
|
} finally {
|
|
await safeFetch.close();
|
|
}
|
|
});
|
|
|
|
it.each(["localhost", "127.0.0.1", "[::1]"])(
|
|
"allows explicitly configured HTTP %s discovery and token traffic on the same origin",
|
|
async (host) => {
|
|
const origin = `http://${host}:8123`;
|
|
const fetchImpl = vi.fn(async () => Response.json({ ok: true }));
|
|
const resolveHostname = vi.fn();
|
|
const safeFetch = secureFetch(
|
|
new URL(`${origin}/mcp`),
|
|
{ allowHttpLocalhost: true },
|
|
{},
|
|
{ fetch: fetchImpl, resolveHostname },
|
|
);
|
|
try {
|
|
await expect(
|
|
safeFetch(`${origin}/.well-known/oauth-protected-resource`),
|
|
).resolves.toHaveProperty("ok", true);
|
|
await expect(
|
|
safeFetch(`${origin}/token`, { method: "POST", body: "fake-token-body" }),
|
|
).resolves.toHaveProperty("ok", true);
|
|
expect(fetchImpl).toHaveBeenCalledTimes(2);
|
|
expect(resolveHostname).not.toHaveBeenCalled();
|
|
} finally {
|
|
await safeFetch.close();
|
|
}
|
|
},
|
|
);
|
|
|
|
it.each(["https://localhost:8123", "https://127.0.0.1:8123", "https://private.example.test"])(
|
|
"applies remote network policy to discovery and token traffic at %s",
|
|
async (origin) => {
|
|
const fetchImpl = vi.fn();
|
|
const safeFetch = secureFetch(
|
|
new URL("http://localhost:8123/mcp"),
|
|
{ allowHttpLocalhost: true },
|
|
{},
|
|
{
|
|
fetch: fetchImpl,
|
|
resolveHostname: async () => [{ address: "10.0.0.1", family: 4 }],
|
|
},
|
|
);
|
|
try {
|
|
await expect(safeFetch(`${origin}/.well-known/oauth-protected-resource`)).rejects.toThrow(
|
|
"private",
|
|
);
|
|
await expect(
|
|
safeFetch(`${origin}/token`, { method: "POST", body: "fake-token-body" }),
|
|
).rejects.toThrow("private");
|
|
expect(fetchImpl).not.toHaveBeenCalled();
|
|
} finally {
|
|
await safeFetch.close();
|
|
}
|
|
},
|
|
);
|
|
|
|
it("keeps SDK protocol headers on the resource origin and strips them on other origins", async () => {
|
|
const safeFetch = secureFetch(
|
|
new URL("https://mcp.example.test/mcp"),
|
|
{},
|
|
{},
|
|
{
|
|
...TEST_NETWORK,
|
|
fetch: vi.fn(async (_input: string | URL | Request, init?: RequestInit) =>
|
|
Response.json(Object.fromEntries(new Headers(init?.headers))),
|
|
),
|
|
},
|
|
);
|
|
const headers = {
|
|
"mcp-session-id": "fake-session",
|
|
"mcp-protocol-version": "2025-11-25",
|
|
"last-event-id": "fake-event",
|
|
};
|
|
try {
|
|
await expect(
|
|
(await safeFetch("https://mcp.example.test/mcp", { headers })).json(),
|
|
).resolves.toEqual({ ...headers, host: "mcp.example.test" });
|
|
await expect(
|
|
(await safeFetch("https://auth.example.test/discovery", { headers })).json(),
|
|
).resolves.toEqual({ host: "auth.example.test" });
|
|
await expect(
|
|
(await safeFetch("https://mcp.example.test:8443/discovery", { headers })).json(),
|
|
).resolves.toEqual({ host: "mcp.example.test:8443" });
|
|
} finally {
|
|
await safeFetch.close();
|
|
}
|
|
});
|
|
|
|
it("lets the SDK refresh a rejected token, persist rotation, and retry the MCP request", async () => {
|
|
const resourceHeaders: string[] = [];
|
|
const persisted: unknown[] = [];
|
|
vi.stubGlobal(
|
|
"fetch",
|
|
vi.fn(async (input: string | URL | Request, init?: RequestInit) => {
|
|
const request = input instanceof Request ? input : new Request(input, init);
|
|
const url = new URL(logicalHref(input, init));
|
|
if (url.href === "https://auth.example.test/token") {
|
|
return Response.json({
|
|
access_token: "fresh-access",
|
|
refresh_token: "rotated-refresh",
|
|
token_type: "bearer",
|
|
expires_in: 3600,
|
|
});
|
|
}
|
|
if (url.href === "https://mcp.example.test/mcp") {
|
|
const authorization = request.headers.get("authorization") ?? "";
|
|
resourceHeaders.push(authorization);
|
|
if (authorization === "Bearer stale-access") {
|
|
return new Response(null, { status: 401, headers: { "WWW-Authenticate": "Bearer" } });
|
|
}
|
|
const message = JSON.parse(await request.text()) as { id?: number; method?: string };
|
|
if (message.method === "initialize") {
|
|
return Response.json({
|
|
jsonrpc: "2.0",
|
|
id: message.id,
|
|
result: {
|
|
protocolVersion: "2025-11-25",
|
|
capabilities: {},
|
|
serverInfo: { name: "test", version: "1" },
|
|
},
|
|
});
|
|
}
|
|
return new Response(null, { status: 202 });
|
|
}
|
|
throw new Error(`Unexpected request: ${request.method} ${url}`);
|
|
}),
|
|
);
|
|
const provider = new StoredMcpOAuthProvider(
|
|
"server-1",
|
|
{
|
|
oauth: {
|
|
redirectUri: "http://127.0.0.1:5173/mcp/oauth/callback",
|
|
tokens: { access_token: "stale-access", refresh_token: "refresh", token_type: "bearer" },
|
|
clientInformation: { client_id: "client-1" },
|
|
discoveryState: {
|
|
authorizationServerUrl: "https://auth.example.test",
|
|
resourceMetadata: {
|
|
resource: "https://mcp.example.test/mcp",
|
|
authorization_servers: ["https://auth.example.test"],
|
|
},
|
|
authorizationServerMetadata: {
|
|
issuer: "https://auth.example.test",
|
|
authorization_endpoint: "https://auth.example.test/authorize",
|
|
token_endpoint: "https://auth.example.test/token",
|
|
response_types_supported: ["code"],
|
|
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
async (material) => {
|
|
persisted.push(structuredClone(material));
|
|
},
|
|
);
|
|
const session = new McpSession();
|
|
|
|
await session.connectRemote({
|
|
url: "https://mcp.example.test/mcp",
|
|
authProvider: provider,
|
|
fallbackToSse: false,
|
|
network: TEST_NETWORK,
|
|
});
|
|
|
|
expect(resourceHeaders[0]).toBe("Bearer stale-access");
|
|
expect(resourceHeaders.slice(1)).not.toHaveLength(0);
|
|
expect(resourceHeaders.slice(1).every((value) => value === "Bearer fresh-access")).toBe(true);
|
|
expect(provider.tokens()).toMatchObject({
|
|
access_token: "fresh-access",
|
|
refresh_token: "rotated-refresh",
|
|
});
|
|
expect(persisted).toHaveLength(1);
|
|
await session.close();
|
|
});
|
|
|
|
it("invalidates a dead refresh token and surfaces reconnect instead of silently losing tools", async () => {
|
|
const persisted: unknown[] = [];
|
|
vi.stubGlobal(
|
|
"fetch",
|
|
vi.fn(async (input: string | URL | Request, init?: RequestInit) => {
|
|
const request = input instanceof Request ? input : new Request(input, init);
|
|
const url = logicalHref(input, init);
|
|
if (url === "https://auth.example.test/token") {
|
|
return Response.json(
|
|
{ error: "invalid_grant", error_description: "refresh token revoked" },
|
|
{ status: 400 },
|
|
);
|
|
}
|
|
if (url === "https://mcp.example.test/mcp") {
|
|
return new Response(null, { status: 401, headers: { "WWW-Authenticate": "Bearer" } });
|
|
}
|
|
throw new Error(`Unexpected request: ${request.method} ${url}`);
|
|
}),
|
|
);
|
|
const provider = new StoredMcpOAuthProvider(
|
|
"server-1",
|
|
{
|
|
oauth: {
|
|
redirectUri: "http://127.0.0.1:5173/mcp/oauth/callback",
|
|
tokens: {
|
|
access_token: "stale-access",
|
|
refresh_token: "dead-refresh",
|
|
token_type: "bearer",
|
|
},
|
|
clientInformation: { client_id: "client-1" },
|
|
discoveryState: {
|
|
authorizationServerUrl: "https://auth.example.test",
|
|
resourceMetadata: {
|
|
resource: "https://mcp.example.test/mcp",
|
|
authorization_servers: ["https://auth.example.test"],
|
|
},
|
|
authorizationServerMetadata: {
|
|
issuer: "https://auth.example.test",
|
|
authorization_endpoint: "https://auth.example.test/authorize",
|
|
token_endpoint: "https://auth.example.test/token",
|
|
response_types_supported: ["code"],
|
|
grant_types_supported: ["authorization_code", "refresh_token"],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
async (material) => {
|
|
persisted.push(structuredClone(material));
|
|
},
|
|
);
|
|
const session = new McpSession();
|
|
|
|
await expect(
|
|
session.connectRemote({
|
|
url: "https://mcp.example.test/mcp",
|
|
authProvider: provider,
|
|
fallbackToSse: false,
|
|
network: TEST_NETWORK,
|
|
}),
|
|
).rejects.toThrow("Reconnect this server");
|
|
|
|
expect(provider.tokens()).toBeUndefined();
|
|
expect(persisted.length).toBeGreaterThanOrEqual(1);
|
|
});
|
|
|
|
// Real HTTP on purpose: undici drops the whole request ("fetch failed",
|
|
// cause "expected non-null body source") when a stream-bodied POST meets a
|
|
// server that answers 401 without draining the body — the exact shape of an
|
|
// OAuth challenge. A stubbed fetch can never catch this.
|
|
it("receives the OAuth 401 challenge from a server that rejects before reading the request body", async () => {
|
|
const server = createServer((req, res) => {
|
|
if (req.method !== "POST" || !req.url?.startsWith("/mcp")) {
|
|
res.statusCode = 404;
|
|
res.end();
|
|
return;
|
|
}
|
|
res.statusCode = 401;
|
|
res.setHeader("WWW-Authenticate", "Bearer");
|
|
res.end();
|
|
});
|
|
await new Promise<void>((resolve) => server.listen(0, "127.0.0.1", resolve));
|
|
const port = (server.address() as AddressInfo).port;
|
|
const provider = new StoredMcpOAuthProvider(
|
|
"server-1",
|
|
{
|
|
oauth: {
|
|
redirectUri: "http://127.0.0.1:5173/mcp/oauth/callback",
|
|
clientInformation: { client_id: "client-1" },
|
|
discoveryState: {
|
|
authorizationServerUrl: `http://127.0.0.1:${port}`,
|
|
authorizationServerMetadata: {
|
|
issuer: `http://127.0.0.1:${port}`,
|
|
authorization_endpoint: `http://127.0.0.1:${port}/authorize`,
|
|
token_endpoint: `http://127.0.0.1:${port}/token`,
|
|
response_types_supported: ["code"],
|
|
},
|
|
},
|
|
},
|
|
},
|
|
async () => {},
|
|
);
|
|
const session = new McpSession();
|
|
try {
|
|
await expect(
|
|
session.connectRemote({
|
|
url: `http://127.0.0.1:${port}/mcp`,
|
|
authProvider: provider,
|
|
fallbackToSse: false,
|
|
urlPolicy: { allowHttpLocalhost: true },
|
|
}),
|
|
).rejects.toThrow("Reconnect this server");
|
|
} finally {
|
|
await session.close();
|
|
await new Promise((resolve) => server.close(resolve));
|
|
}
|
|
});
|
|
|
|
it("strips sensitive headers when falling back to the endpoint origin", async () => {
|
|
const seen: Record<string, string>[] = [];
|
|
const inner = vi.fn(async (input: string | URL | Request, init?: RequestInit) => {
|
|
const request = input instanceof Request ? input : new Request(input, init);
|
|
if (request.url === "https://private.example.test/data") throw new TypeError("fetch failed");
|
|
seen.push(Object.fromEntries(new Headers(init?.headers).entries()));
|
|
return Response.json({ ok: true });
|
|
});
|
|
const fallback = withEndpointOriginFallback(
|
|
"https://endpoint.example.test",
|
|
inner as typeof fetch,
|
|
);
|
|
|
|
const response = await fallback("https://private.example.test/data", {
|
|
headers: { authorization: "Bearer secret", "x-custom": "keep-me" },
|
|
});
|
|
|
|
expect(response.ok).toBe(true);
|
|
const retry = seen[0] ?? {};
|
|
expect(retry.authorization).toBeUndefined();
|
|
expect(retry.cookie).toBeUndefined();
|
|
expect(retry["x-custom"]).toBe("keep-me");
|
|
});
|
|
|
|
it("keeps sensitive headers on same-origin and first-attempt requests", async () => {
|
|
const inner = vi.fn(async (input: string | URL | Request, init?: RequestInit) => {
|
|
const request = input instanceof Request ? input : new Request(input, init);
|
|
return Response.json({ authorization: request.headers.get("authorization") });
|
|
});
|
|
const fallback = withEndpointOriginFallback(
|
|
"https://endpoint.example.test",
|
|
inner as typeof fetch,
|
|
);
|
|
|
|
const direct = await fallback("https://endpoint.example.test/api", {
|
|
headers: { authorization: "Bearer secret" },
|
|
});
|
|
await expect(direct.json()).resolves.toEqual({ authorization: "Bearer secret" });
|
|
});
|
|
|
|
it("strips configured credentials from localhost HTTP requests", async () => {
|
|
let seen: Record<string, string> = {};
|
|
const safeFetch = secureFetch(
|
|
new URL("http://localhost:8123/mcp"),
|
|
{ allowHttpLocalhost: true },
|
|
{
|
|
allowedHeaders: ["authorization", "x-api-key"],
|
|
headers: { Authorization: "Bearer stored", "X-Api-Key": "stored-key" },
|
|
},
|
|
{
|
|
fetch: vi.fn(async (input: string | URL | Request, init?: RequestInit) => {
|
|
const request = input instanceof Request ? input : new Request(input, init);
|
|
seen = Object.fromEntries(request.headers.entries());
|
|
return Response.json({ ok: true });
|
|
}),
|
|
},
|
|
);
|
|
|
|
await safeFetch("http://localhost:8123/mcp", {
|
|
method: "POST",
|
|
headers: {
|
|
Authorization: "Bearer sdk",
|
|
"X-Api-Key": "sdk-key",
|
|
"Content-Type": "application/json",
|
|
},
|
|
body: "{}",
|
|
});
|
|
|
|
expect(seen.authorization).toBeUndefined();
|
|
expect(seen["x-api-key"]).toBeUndefined();
|
|
expect(seen["content-type"]).toBe("application/json");
|
|
});
|
|
|
|
it("keeps configured credentials for HTTPS requests", async () => {
|
|
let seen: Record<string, string> = {};
|
|
const safeFetch = secureFetch(
|
|
new URL("https://mcp.example.test/mcp"),
|
|
{},
|
|
{
|
|
allowedHeaders: ["authorization", "x-api-key"],
|
|
headers: { Authorization: "Bearer stored", "X-Api-Key": "stored-key" },
|
|
},
|
|
{
|
|
resolveHostname: TEST_NETWORK.resolveHostname,
|
|
fetch: vi.fn(async (input: string | URL | Request, init?: RequestInit) => {
|
|
const request = input instanceof Request ? input : new Request(input, init);
|
|
seen = Object.fromEntries(request.headers.entries());
|
|
return Response.json({ ok: true });
|
|
}),
|
|
},
|
|
);
|
|
|
|
await safeFetch("https://mcp.example.test/mcp", { method: "POST", body: "{}" });
|
|
|
|
expect(seen.authorization).toBe("Bearer stored");
|
|
expect(seen["x-api-key"]).toBe("stored-key");
|
|
});
|
|
|
|
it("drops request headers outside the configured allowlist", async () => {
|
|
let seen: Record<string, string> = {};
|
|
const safeFetch = secureFetch(
|
|
new URL("https://mcp.example.test/mcp"),
|
|
{},
|
|
{ allowedHeaders: ["accept"] },
|
|
{
|
|
resolveHostname: TEST_NETWORK.resolveHostname,
|
|
fetch: vi.fn(async (input: string | URL | Request, init?: RequestInit) => {
|
|
const request = input instanceof Request ? input : new Request(input, init);
|
|
seen = Object.fromEntries(request.headers.entries());
|
|
return Response.json({ ok: true });
|
|
}),
|
|
},
|
|
);
|
|
|
|
await safeFetch("https://mcp.example.test/mcp", {
|
|
headers: { Accept: "application/json", "X-Untrusted": "drop-me" },
|
|
});
|
|
|
|
expect(seen.accept).toBe("application/json");
|
|
expect(seen["x-untrusted"]).toBeUndefined();
|
|
});
|
|
|
|
it("honours init header replacement for Request inputs", async () => {
|
|
let seen: Record<string, string> = {};
|
|
const safeFetch = secureFetch(
|
|
new URL("https://mcp.example.test/mcp"),
|
|
{},
|
|
{},
|
|
{
|
|
resolveHostname: TEST_NETWORK.resolveHostname,
|
|
fetch: vi.fn(async (input: string | URL | Request, init?: RequestInit) => {
|
|
const request = input instanceof Request ? input : new Request(input, init);
|
|
seen = Object.fromEntries(request.headers.entries());
|
|
return Response.json({ ok: true });
|
|
}),
|
|
},
|
|
);
|
|
const request = new Request("https://mcp.example.test/mcp", {
|
|
headers: { Authorization: "Bearer stale", "Content-Type": "application/json" },
|
|
});
|
|
|
|
await safeFetch(request, { headers: { Accept: "application/json" } });
|
|
|
|
expect(seen.accept).toBe("application/json");
|
|
expect(seen.authorization).toBeUndefined();
|
|
expect(seen["content-type"]).toBeUndefined();
|
|
});
|
|
|
|
it("does not forward configured resource credentials to another origin", async () => {
|
|
let seen: Record<string, string> = {};
|
|
const safeFetch = secureFetch(
|
|
new URL("https://mcp.example.test/mcp"),
|
|
{},
|
|
{ headers: { Authorization: "Bearer stored", "X-Api-Key": "stored-key" } },
|
|
{
|
|
resolveHostname: TEST_NETWORK.resolveHostname,
|
|
fetch: vi.fn(async (input: string | URL | Request, init?: RequestInit) => {
|
|
const request = input instanceof Request ? input : new Request(input, init);
|
|
seen = Object.fromEntries(request.headers.entries());
|
|
return Response.json({ ok: true });
|
|
}),
|
|
},
|
|
);
|
|
|
|
await safeFetch("https://auth.example.test/token", {
|
|
headers: { Authorization: "Bearer stored", "X-Api-Key": "stored-key" },
|
|
});
|
|
|
|
expect(seen.authorization).toBeUndefined();
|
|
expect(seen["x-api-key"]).toBeUndefined();
|
|
});
|
|
|
|
it("does not forward configured credential values under another header name", async () => {
|
|
let seen: Record<string, string> = {};
|
|
const safeFetch = secureFetch(
|
|
new URL("https://mcp.example.test/mcp"),
|
|
{},
|
|
{ headers: { "X-Api-Key": "stored-key" } },
|
|
{
|
|
resolveHostname: TEST_NETWORK.resolveHostname,
|
|
fetch: vi.fn(async (input: string | URL | Request, init?: RequestInit) => {
|
|
const request = input instanceof Request ? input : new Request(input, init);
|
|
seen = Object.fromEntries(request.headers.entries());
|
|
return Response.json({ ok: true });
|
|
}),
|
|
},
|
|
);
|
|
|
|
await safeFetch("https://auth.example.test/token", {
|
|
headers: { Authorization: "stored-key" },
|
|
});
|
|
|
|
expect(seen.authorization).toBeUndefined();
|
|
});
|
|
|
|
it("never retries a failed write against the endpoint origin", async () => {
|
|
const inner = vi.fn(async () => {
|
|
throw new TypeError("fetch failed");
|
|
});
|
|
const fallback = withEndpointOriginFallback(
|
|
"https://endpoint.example.test",
|
|
inner as typeof fetch,
|
|
);
|
|
|
|
await expect(
|
|
fallback("https://private.example.test/register", {
|
|
method: "POST",
|
|
body: "client_secret=secret",
|
|
}),
|
|
).rejects.toThrow("fetch failed");
|
|
expect(inner).toHaveBeenCalledTimes(1);
|
|
});
|
|
});
|