Redact MCP debug request logs (#842)
This commit is contained in:
@@ -41,6 +41,52 @@ describe("paseo daemon bootstrap", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("redacts Agent MCP debug request credentials and bodies", async () => {
|
||||
const logLines: string[] = [];
|
||||
const logger = pino(
|
||||
{ level: "debug" },
|
||||
{
|
||||
write: (line: string) => {
|
||||
logLines.push(line);
|
||||
},
|
||||
},
|
||||
);
|
||||
const daemonHandle = await createTestPaseoDaemon({
|
||||
logger,
|
||||
mcpDebug: true,
|
||||
});
|
||||
|
||||
try {
|
||||
const response = await fetch(`http://127.0.0.1:${daemonHandle.port}/mcp/agents`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: "Bearer secret-debug-token",
|
||||
"Content-Type": "application/json",
|
||||
},
|
||||
body: JSON.stringify({
|
||||
jsonrpc: "2.0",
|
||||
id: 1,
|
||||
method: "tools/call",
|
||||
params: {
|
||||
apiKey: "secret-body-token",
|
||||
},
|
||||
}),
|
||||
});
|
||||
|
||||
expect(response.status).toBe(400);
|
||||
const logs = logLines.join("\n");
|
||||
expect(logs).toContain("Agent MCP request");
|
||||
expect(logs).toContain("[redacted]");
|
||||
expect(logs).toContain('"method":"tools/call"');
|
||||
expect(logs).toContain('"hasParams":true');
|
||||
expect(logs).not.toContain("secret-debug-token");
|
||||
expect(logs).not.toContain("secret-body-token");
|
||||
expect(logs).not.toContain("apiKey");
|
||||
} finally {
|
||||
await daemonHandle.close();
|
||||
}
|
||||
});
|
||||
|
||||
test("fails fast when OpenAI speech provider is configured without credentials", async () => {
|
||||
const paseoHomeRoot = await mkdtemp(path.join(os.tmpdir(), "paseo-openai-config-"));
|
||||
const paseoHome = path.join(paseoHomeRoot, ".paseo");
|
||||
|
||||
@@ -141,6 +141,9 @@ import { createRequireBearerMiddleware, type DaemonAuthConfig } from "./auth.js"
|
||||
|
||||
type AgentMcpTransportMap = Map<string, StreamableHTTPServerTransport>;
|
||||
|
||||
const MAX_MCP_DEBUG_BATCH_ITEMS = 10;
|
||||
const REDACTED_LOG_VALUE = "[redacted]";
|
||||
|
||||
function formatHostForHttpUrl(host: string): string {
|
||||
return host.includes(":") && !host.startsWith("[") ? `[${host}]` : host;
|
||||
}
|
||||
@@ -155,6 +158,38 @@ function createAgentMcpBaseUrl(listenTarget: ListenTarget | null): string | null
|
||||
).toString();
|
||||
}
|
||||
|
||||
function summarizeAgentMcpDebugMessage(body: unknown): Record<string, unknown> {
|
||||
if (!body || typeof body !== "object" || Array.isArray(body)) {
|
||||
return {
|
||||
type: body === null ? "null" : typeof body,
|
||||
};
|
||||
}
|
||||
|
||||
const record = body as Record<string, unknown>;
|
||||
const method = typeof record.method === "string" ? record.method : undefined;
|
||||
return {
|
||||
type: "object",
|
||||
...(typeof record.jsonrpc === "string" ? { jsonrpc: record.jsonrpc } : {}),
|
||||
...(method ? { method } : {}),
|
||||
hasId: Object.prototype.hasOwnProperty.call(record, "id"),
|
||||
hasParams: Object.prototype.hasOwnProperty.call(record, "params"),
|
||||
};
|
||||
}
|
||||
|
||||
function summarizeAgentMcpDebugBody(body: unknown): Record<string, unknown> {
|
||||
if (!Array.isArray(body)) {
|
||||
return summarizeAgentMcpDebugMessage(body);
|
||||
}
|
||||
|
||||
const messages = body.slice(0, MAX_MCP_DEBUG_BATCH_ITEMS).map(summarizeAgentMcpDebugMessage);
|
||||
return {
|
||||
type: "batch",
|
||||
count: body.length,
|
||||
messages,
|
||||
...(body.length > messages.length ? { omitted: body.length - messages.length } : {}),
|
||||
};
|
||||
}
|
||||
|
||||
export type PaseoOpenAIConfig = OpenAiSpeechProviderConfig;
|
||||
export type PaseoLocalSpeechConfig = LocalSpeechProviderConfig;
|
||||
|
||||
@@ -671,8 +706,8 @@ export async function createPaseoDaemon(
|
||||
method: req.method,
|
||||
url: req.originalUrl,
|
||||
sessionId: req.header("mcp-session-id"),
|
||||
authorization: req.header("authorization"),
|
||||
body: req.body,
|
||||
authorization: req.header("authorization") ? REDACTED_LOG_VALUE : undefined,
|
||||
body: summarizeAgentMcpDebugBody(req.body),
|
||||
},
|
||||
"Agent MCP request",
|
||||
);
|
||||
|
||||
@@ -18,6 +18,7 @@ interface TestPaseoDaemonOptions {
|
||||
corsAllowedOrigins?: string[];
|
||||
listen?: string;
|
||||
logger?: Parameters<typeof createPaseoDaemon>[1];
|
||||
mcpDebug?: boolean;
|
||||
relayEnabled?: boolean;
|
||||
relayEndpoint?: string;
|
||||
agentClients?: Partial<Record<AgentProvider, AgentClient>>;
|
||||
@@ -152,7 +153,7 @@ async function prepareTestDaemonConfig(
|
||||
hostnames: true,
|
||||
mcpEnabled: true,
|
||||
staticDir,
|
||||
mcpDebug: false,
|
||||
mcpDebug: options.mcpDebug ?? false,
|
||||
agentClients: options.agentClients ?? createTestAgentClients(),
|
||||
agentStoragePath: path.join(paseoHome, "agents"),
|
||||
relayEnabled: options.relayEnabled ?? false,
|
||||
|
||||
Reference in New Issue
Block a user