From 1d9cc323263b9667cd95f5a3919aa7759943b3a1 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 30 Oct 2026 16:43:49 -0700 Subject: [PATCH 1/4] test(search): guard Fireflies response diagnostic privacy --- .../lib/sim-search/live/meeting-mcp.test.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/apps/sim/lib/sim-search/live/meeting-mcp.test.ts b/apps/sim/lib/sim-search/live/meeting-mcp.test.ts index 00fbdae8334..f564765f8cc 100644 --- a/apps/sim/lib/sim-search/live/meeting-mcp.test.ts +++ b/apps/sim/lib/sim-search/live/meeting-mcp.test.ts @@ -1,3 +1,4 @@ +import { getAllMockLoggers } from '@sim/testing/mocks/logger.mock' import { describe, expect, it } from 'vitest' import { readFirefliesMcp, searchFirefliesMcp } from '@/lib/sim-search/live/fireflies-mcp' import { readGranolaMcp, searchGranolaMcp } from '@/lib/sim-search/live/granola-mcp' @@ -157,6 +158,30 @@ describe('meeting MCP provider wire contracts', () => { await expect(searchFirefliesMcp(client, input)).rejects.toThrow('unsupported') }) + it('diagnoses unsupported Fireflies envelopes without logging private values or arbitrary keys', async () => { + const privateMarker = 'PRIVATE_MEETING_OR_CREDENTIAL' + const client: ManagedSearchMcpClient = { + async call() { + return { + text: privateMarker.repeat(1000), + data: { error: privateMarker }, + result: { items: [{ title: privateMarker }] }, + ...Object.fromEntries( + Array.from({ length: 1000 }, (_, index) => [`${privateMarker}_${index}`, privateMarker]) + ), + } + }, + } + await expect(searchFirefliesMcp(client, { ...input, query: privateMarker })).rejects.toThrow( + 'unsupported' + ) + const warnings = getAllMockLoggers().flatMap((logger) => logger.warn.mock.calls) + expect(warnings).toHaveLength(1) + const serialized = JSON.stringify(warnings) + expect(serialized).not.toContain(privateMarker) + expect(serialized.length).toBeLessThan(2000) + }) + it('rejects invalid continuation offsets before Fireflies receives a query', async () => { const client = { async call() { From e1dde28c59ee21981cfe9dcf149867a156c1657a Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 30 Oct 2026 16:48:43 -0700 Subject: [PATCH 2/4] chore(search): diagnose unsupported Fireflies response shapes --- apps/sim/lib/sim-search/live/fireflies-mcp.ts | 50 ++++++++++++++++++- 1 file changed, 49 insertions(+), 1 deletion(-) diff --git a/apps/sim/lib/sim-search/live/fireflies-mcp.ts b/apps/sim/lib/sim-search/live/fireflies-mcp.ts index 13bfcd74d0d..395731178ff 100644 --- a/apps/sim/lib/sim-search/live/fireflies-mcp.ts +++ b/apps/sim/lib/sim-search/live/fireflies-mcp.ts @@ -1,3 +1,4 @@ +import { createLogger } from '@sim/logger' import { toArray, toRecord } from '@sim/utils/object' import { nativeText } from '@/lib/sim-search/live/dates' import { NativeSearchError, string } from '@/lib/sim-search/live/http' @@ -7,6 +8,45 @@ import type { NativeDocument, NativePage, NativeSearchInput } from '@/lib/sim-se const MAX_MEETING_CONTENT = 200_000 const UTC_DAY_MS = 86_400_000 +const logger = createLogger('FirefliesMcpSearch') + +/** Only fixed envelope keys and value kinds may enter diagnostics, never provider keys or values. */ +function responseShape(value: unknown) { + const record = toRecord(value) + const kind = (entry: unknown) => + entry === null ? 'null' : Array.isArray(entry) ? 'array' : typeof entry + return { + type: kind(value), + fields: Object.fromEntries( + [ + 'transcripts', + 'data', + 'result', + 'results', + 'items', + 'output', + 'text', + 'error', + 'errors', + ].flatMap((key) => (Object.hasOwn(record, key) ? [[key, kind(record[key])]] : [])) + ), + } +} + +function responseTextShape(value: unknown) { + if (typeof value !== 'string') return undefined + const text = value.trim() + const fenced = text.match(/^```(?:json)?\r?\n([\s\S]*)\r?\n```$/i)?.[1] + let jsonShape: ReturnType | undefined + try { + jsonShape = responseShape(JSON.parse(fenced ?? text)) + } catch {} + return { + length: value.length, + format: fenced !== undefined ? 'code_fence' : jsonShape ? 'json' : 'text', + ...(jsonShape ? { jsonShape } : {}), + } +} function requireMeetingId(id: string): string { if (!/^[\w-]{1,200}$/.test(id)) @@ -139,11 +179,19 @@ export async function searchFirefliesMcp( const rows = Array.isArray(result) ? result : (data.transcripts ?? toRecord(data.data).transcripts) - if (!Array.isArray(rows)) + if (!Array.isArray(rows)) { + logger.warn('Unsupported Fireflies transcript list response shape', { + response: responseShape(result), + data: responseShape(data.data), + result: responseShape(data.result), + output: responseShape(data.output), + text: responseTextShape(typeof result === 'string' ? result : data.text), + }) throw new NativeSearchError( 'unavailable', 'Fireflies returned an unsupported transcript list format.' ) + } const documents = rows.slice(0, limit).flatMap((value) => { const document = meetingDocument(toRecord(value)) return document ? [document] : [] From 722b3a55b1d35538ee23b62d3eb29d5bfe0992cf Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 30 Oct 2026 16:55:31 -0700 Subject: [PATCH 3/4] test(search): cover encoded and oversized diagnostic text --- .../lib/sim-search/live/meeting-mcp.test.ts | 74 +++++++++++++------ 1 file changed, 52 insertions(+), 22 deletions(-) diff --git a/apps/sim/lib/sim-search/live/meeting-mcp.test.ts b/apps/sim/lib/sim-search/live/meeting-mcp.test.ts index f564765f8cc..62ceaf86aa8 100644 --- a/apps/sim/lib/sim-search/live/meeting-mcp.test.ts +++ b/apps/sim/lib/sim-search/live/meeting-mcp.test.ts @@ -158,29 +158,59 @@ describe('meeting MCP provider wire contracts', () => { await expect(searchFirefliesMcp(client, input)).rejects.toThrow('unsupported') }) - it('diagnoses unsupported Fireflies envelopes without logging private values or arbitrary keys', async () => { - const privateMarker = 'PRIVATE_MEETING_OR_CREDENTIAL' - const client: ManagedSearchMcpClient = { - async call() { - return { - text: privateMarker.repeat(1000), - data: { error: privateMarker }, - result: { items: [{ title: privateMarker }] }, - ...Object.fromEntries( - Array.from({ length: 1000 }, (_, index) => [`${privateMarker}_${index}`, privateMarker]) - ), - } - }, + it.each(['text', 'json', 'code_fence', 'oversized'] as const)( + 'diagnoses unsupported Fireflies %s envelopes without logging private values or arbitrary keys', + async (format) => { + const privateMarker = 'PRIVATE_MEETING_OR_CREDENTIAL' + const json = JSON.stringify({ items: [{ title: privateMarker }], [privateMarker]: 'secret' }) + const text = + format === 'oversized' + ? JSON.stringify({ items: [privateMarker.repeat(3000)] }) + : format === 'code_fence' + ? ['```json', json, '```'].join('\n') + : format === 'json' + ? json + : privateMarker.repeat(1000) + const client: ManagedSearchMcpClient = { + async call() { + return { + text, + data: { error: privateMarker }, + result: { items: [{ title: privateMarker }] }, + ...Object.fromEntries( + Array.from({ length: 1000 }, (_, index) => [ + `${privateMarker}_${index}`, + privateMarker, + ]) + ), + } + }, + } + await expect(searchFirefliesMcp(client, { ...input, query: privateMarker })).rejects.toThrow( + 'unsupported' + ) + const warnings = getAllMockLoggers().flatMap((logger) => logger.warn.mock.calls) + expect(warnings).toHaveLength(1) + expect(warnings[0]?.[1]).toMatchObject({ + response: { type: 'object', fields: { text: 'string', data: 'object', result: 'object' } }, + data: { type: 'object', fields: { error: 'string' } }, + result: { type: 'object', fields: { items: 'array' } }, + text: + format === 'oversized' + ? { length: text.length, inspection: 'skipped_size_limit' } + : { + length: text.length, + format, + ...(format === 'text' + ? {} + : { jsonShape: { type: 'object', fields: { items: 'array' } } }), + }, + }) + const serialized = JSON.stringify(warnings) + expect(serialized).not.toContain(privateMarker) + expect(serialized.length).toBeLessThan(2000) } - await expect(searchFirefliesMcp(client, { ...input, query: privateMarker })).rejects.toThrow( - 'unsupported' - ) - const warnings = getAllMockLoggers().flatMap((logger) => logger.warn.mock.calls) - expect(warnings).toHaveLength(1) - const serialized = JSON.stringify(warnings) - expect(serialized).not.toContain(privateMarker) - expect(serialized.length).toBeLessThan(2000) - }) + ) it('rejects invalid continuation offsets before Fireflies receives a query', async () => { const client = { From 4fb0ae69e08917a402cb1ac68a00bc73fe33fd92 Mon Sep 17 00:00:00 2001 From: Waleed Latif Date: Fri, 30 Oct 2026 17:00:00 -0700 Subject: [PATCH 4/4] fix(search): retain bounded provider diagnostics in production --- apps/sim/lib/sim-search/live/fireflies-mcp.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/apps/sim/lib/sim-search/live/fireflies-mcp.ts b/apps/sim/lib/sim-search/live/fireflies-mcp.ts index 395731178ff..6b623831827 100644 --- a/apps/sim/lib/sim-search/live/fireflies-mcp.ts +++ b/apps/sim/lib/sim-search/live/fireflies-mcp.ts @@ -7,8 +7,9 @@ import { boundedMeetingContent } from '@/lib/sim-search/live/meeting-content' import type { NativeDocument, NativePage, NativeSearchInput } from '@/lib/sim-search/live/types' const MAX_MEETING_CONTENT = 200_000 +const MAX_DIAGNOSTIC_TEXT_LENGTH = 65_536 const UTC_DAY_MS = 86_400_000 -const logger = createLogger('FirefliesMcpSearch') +const logger = createLogger('FirefliesMcpSearch', { logLevel: 'WARN' }) /** Only fixed envelope keys and value kinds may enter diagnostics, never provider keys or values. */ function responseShape(value: unknown) { @@ -35,6 +36,8 @@ function responseShape(value: unknown) { function responseTextShape(value: unknown) { if (typeof value !== 'string') return undefined + if (value.length > MAX_DIAGNOSTIC_TEXT_LENGTH) + return { length: value.length, inspection: 'skipped_size_limit' } const text = value.trim() const fenced = text.match(/^```(?:json)?\r?\n([\s\S]*)\r?\n```$/i)?.[1] let jsonShape: ReturnType | undefined