Skip to content

Commit b35dfd5

Browse files
Bill LeoutsakosBill Leoutsakos
authored andcommitted
fix(quickbooks): redact attachment access URLs
1 parent e78f232 commit b35dfd5

6 files changed

Lines changed: 78 additions & 8 deletions

File tree

apps/sim/app/api/tools/quickbooks/documents.test.ts

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,15 @@ describe('QuickBooks document API routes', () => {
149149

150150
it('creates a note attachment with one JSON request', async () => {
151151
mockFetch.mockResolvedValueOnce(
152-
Response.json({ Attachable: { Id: '9', Note: 'Audit note' }, time: '2026-08-02' })
152+
Response.json({
153+
Attachable: {
154+
Id: '9',
155+
Note: 'Audit note',
156+
TempDownloadUri: 'https://example.invalid/temp?token=secret',
157+
ThumbnailTempDownloadUri: 'https://example.invalid/thumbnail?token=secret',
158+
},
159+
time: '2026-08-02',
160+
})
153161
)
154162

155163
const response = await addAttachment(
@@ -165,6 +173,7 @@ describe('QuickBooks document API routes', () => {
165173

166174
expect(response.status).toBe(200)
167175
expect(body.output).toMatchObject({ attachmentId: '9', attachmentKind: 'note' })
176+
expect(body.output.attachment).toEqual({ Id: '9', Note: 'Audit note' })
168177
expect(mockFetch).toHaveBeenCalledTimes(1)
169178
expect(JSON.parse(mockFetch.mock.calls[0][1].body)).toMatchObject({
170179
Note: 'Audit note',

apps/sim/tools/generated/tool-metadata.ts

Lines changed: 1 addition & 1 deletion
Large diffs are not rendered by default.

apps/sim/tools/quickbooks/documents.test.ts

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import {
55
getQuickBooksAttachmentTarget,
66
parseQuickBooksAttachableResponse,
77
QUICKBOOKS_FILE_TOOL_RESPONSE_MAX_BYTES,
8+
sanitizeQuickBooksAttachable,
89
sanitizeQuickBooksFileName,
910
validateQuickBooksAttachmentFileType,
1011
} from '@/tools/quickbooks/documents_utils'
@@ -191,6 +192,45 @@ describe('QuickBooks attachment metadata reads', () => {
191192
})
192193
})
193194

195+
it('removes Intuit attachment access URLs from parsed and listed metadata', async () => {
196+
const attachment = {
197+
Id: '9',
198+
FileName: 'receipt.pdf',
199+
FileAccessUri: 'https://example.invalid/file',
200+
TempDownloadUri: 'https://example.invalid/temp?token=secret',
201+
TemporaryDownloadUri: 'https://example.invalid/temporary?token=secret',
202+
ThumbnailFileAccessUri: 'https://example.invalid/thumbnail',
203+
ThumbnailTempDownloadUri: 'https://example.invalid/thumbnail-temp?token=secret',
204+
}
205+
206+
expect(sanitizeQuickBooksAttachable(attachment)).toEqual({
207+
Id: '9',
208+
FileName: 'receipt.pdf',
209+
})
210+
211+
await expect(
212+
parseQuickBooksAttachableResponse(Response.json({ Attachable: attachment }))
213+
).resolves.toEqual({
214+
attachment: { Id: '9', FileName: 'receipt.pdf' },
215+
time: null,
216+
})
217+
218+
const params: QuickBooksReadAttachmentsParams = {
219+
...auth,
220+
readMode: 'list',
221+
targetType: 'invoice',
222+
targetId: '88',
223+
}
224+
await expect(
225+
quickbooksReadAttachmentsTool.transformResponse!(
226+
Response.json({ QueryResponse: { Attachable: [attachment] } }),
227+
params
228+
)
229+
).resolves.toMatchObject({
230+
output: { items: [{ Id: '9', FileName: 'receipt.pdf' }] },
231+
})
232+
})
233+
194234
it('surfaces sanitized faults from successful upload envelopes', async () => {
195235
const response = Response.json({
196236
AttachableResponse: [

apps/sim/tools/quickbooks/documents_utils.ts

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,25 @@ export interface QuickBooksAttachableEnvelope {
118118
time?: string
119119
}
120120

121+
/**
122+
* Removes Intuit-managed attachment URLs before records enter tool outputs or execution logs.
123+
* These URLs can be short-lived capability links and are not needed by callers because file
124+
* downloads go through the authenticated Download Attachment operation.
125+
*/
126+
export function sanitizeQuickBooksAttachable(
127+
attachment: QuickBooksAttachable
128+
): QuickBooksAttachable {
129+
const {
130+
FileAccessUri: _fileAccessUri,
131+
TempDownloadUri: _tempDownloadUri,
132+
TemporaryDownloadUri: _temporaryDownloadUri,
133+
ThumbnailFileAccessUri: _thumbnailFileAccessUri,
134+
ThumbnailTempDownloadUri: _thumbnailTempDownloadUri,
135+
...safeAttachment
136+
} = attachment
137+
return safeAttachment as QuickBooksAttachable
138+
}
139+
121140
export async function parseQuickBooksAttachableResponse(
122141
response: Response
123142
): Promise<{ attachment: QuickBooksAttachable; time: string | null }> {
@@ -140,7 +159,10 @@ export async function parseQuickBooksAttachableResponse(
140159
throw new Error('QuickBooks Attachable response is missing a valid attachment ID')
141160
}
142161
const responseTime = data.time ?? data.AttachableResponse?.[0]?.time
143-
return { attachment, time: typeof responseTime === 'string' ? responseTime : null }
162+
return {
163+
attachment: sanitizeQuickBooksAttachable(attachment),
164+
time: typeof responseTime === 'string' ? responseTime : null,
165+
}
144166
}
145167

146168
export function buildQuickBooksAttachableMetadata(

apps/sim/tools/quickbooks/read_attachments.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import {
44
escapeQuickBooksQueryLiteral,
55
getQuickBooksAttachmentTarget,
66
parseQuickBooksAttachableResponse,
7+
sanitizeQuickBooksAttachable,
78
} from '@/tools/quickbooks/documents_utils'
89
import type {
910
QuickBooksAttachable,
@@ -144,9 +145,10 @@ export const quickbooksReadAttachmentsTool: ToolConfig<
144145
) {
145146
throw new Error('QuickBooks Attachable response is missing QueryResponse')
146147
}
147-
const items = data.QueryResponse.Attachable ?? []
148-
if (!Array.isArray(items))
148+
const attachments = data.QueryResponse.Attachable ?? []
149+
if (!Array.isArray(attachments))
149150
throw new Error('QuickBooks Attachable response contains a malformed attachment list')
151+
const items = attachments.map(sanitizeQuickBooksAttachable)
150152
const startPosition = Number.isInteger(data.QueryResponse.startPosition)
151153
? data.QueryResponse.startPosition!
152154
: pagination.startPosition

apps/sim/tools/quickbooks/types.ts

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -443,9 +443,6 @@ export interface QuickBooksAttachable {
443443
Size?: number
444444
Note?: string
445445
Category?: string
446-
TemporaryDownloadUri?: string
447-
FileAccessUri?: string
448-
TempDownloadUri?: string
449446
AttachableRef?: QuickBooksAttachableReference[]
450447
MetaData?: QuickBooksMetaData
451448
domain?: string

0 commit comments

Comments
 (0)