Skip to content

Commit c6d8c3c

Browse files
authored
fix(selectors): redact email path segments from provider failure logs; correct table timeout guidance (#9908)
* fix(selectors): redact email path segments from provider failure logs; correct table timeout guidance * fix(table): keep timeout guidance to sorts and range filters
1 parent 3453077 commit c6d8c3c

3 files changed

Lines changed: 31 additions & 6 deletions

File tree

‎apps/sim/lib/selectors/server/providers/http.test.ts‎

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import { getMockLogger } from '@sim/testing/mocks/logger.mock'
12
import { afterAll, beforeEach, describe, expect, it, vi } from 'vitest'
23
import {
34
SelectorConnectionUnavailableError,
@@ -96,4 +97,23 @@ describe('provider HTTP selector boundary', () => {
9697
expect(cancel).toHaveBeenCalledOnce()
9798
}
9899
)
100+
101+
it('logs a failed provider call without its query string, search text, or email path segments', async () => {
102+
mockFetch.mockResolvedValueOnce(new Response('{}', { status: 404 }))
103+
104+
await expect(
105+
fetchProviderJson(
106+
"https://www.googleapis.com/calendar/v3/calendars/jane%40example.com/root/search(q='payroll')?access_token=secret"
107+
)
108+
).rejects.toBeInstanceOf(SelectorOptionsUnavailableError)
109+
110+
expect(getMockLogger('SelectorProviderHttp').warn).toHaveBeenCalledWith(
111+
'Selector provider request failed',
112+
{
113+
endpoint: 'www.googleapis.com/calendar/v3/calendars/{redacted}/root/search()',
114+
reason: 'status',
115+
status: 404,
116+
}
117+
)
118+
})
99119
})

‎apps/sim/lib/selectors/server/providers/http.ts‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -48,13 +48,17 @@ async function readBoundedBody(response: Response): Promise<string> {
4848
}
4949

5050
/**
51-
* Host and path of a provider request. The query string is dropped and OData-style path
52-
* arguments (`search(q='...')`) are blanked, since both can carry tokens or a user's search text.
51+
* Host and path of a provider request, with the parts that can carry user data removed: the query
52+
* string (tokens, filters), OData-style path arguments (`search(q='...')` holds search text), and
53+
* any segment holding an email address (a Google calendar id is usually its owner's address).
5354
*/
5455
function providerEndpoint(input: RequestInfo | URL): string | undefined {
5556
try {
5657
const url = new URL(input instanceof Request ? input.url : input)
57-
return `${url.host}${url.pathname.replace(/\([^/]*\)/g, '()')}`
58+
const path = url.pathname
59+
.replace(/\([^/]*\)/g, '()')
60+
.replace(/[^/]*(?:@|%40)[^/]*/gi, '{redacted}')
61+
return `${url.host}${path}`
5862
} catch {
5963
return undefined
6064
}

‎apps/sim/lib/table/application/rows.ts‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -506,9 +506,10 @@ async function throwValidationResponse(
506506
}
507507

508508
/**
509-
* A structured row query cancelled by its own `statement_timeout`: the query shape costs more than
510-
* the table's size allows. Sorting or range-filtering on a stored column reads every row's data, so it is the
511-
* caller's to narrow, and retrying the same query only times out again.
509+
* A structured row query cancelled by its own `statement_timeout`. Sorting or range-filtering on a
510+
* stored column reads every candidate row's full data, so on a large table the query only finishes
511+
* when that data happens to be cached. The fix is the caller's (narrow the query), so this is a 400
512+
* with an actionable message rather than an opaque 500.
512513
*/
513514
function rethrowReadQueryError(error: unknown, tableId: string): never {
514515
if (getPostgresCancellationReason(error) === 'statement_timeout') {

0 commit comments

Comments
 (0)