Skip to content

Commit 3645d83

Browse files
committed
fix(security): extend file access checks to remaining tool routes
1 parent d71824c commit 3645d83

9 files changed

Lines changed: 58 additions & 13 deletions

File tree

apps/sim/app/api/tools/microsoft_teams/write_channel/route.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
2020
try {
2121
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
2222

23-
if (!authResult.success) {
23+
if (!authResult.success || !authResult.userId) {
2424
logger.warn(`[${requestId}] Unauthorized Teams channel write attempt: ${authResult.error}`)
2525
return NextResponse.json(
2626
{
@@ -31,10 +31,11 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
3131
)
3232
}
3333

34+
const userId = authResult.userId
3435
logger.info(
3536
`[${requestId}] Authenticated Teams channel write request via ${authResult.authType}`,
3637
{
37-
userId: authResult.userId,
38+
userId,
3839
}
3940
)
4041

@@ -54,6 +55,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
5455
accessToken: validatedData.accessToken,
5556
requestId,
5657
logger,
58+
userId,
5759
})
5860

5961
let messageContent = validatedData.content

apps/sim/app/api/tools/microsoft_teams/write_chat/route.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
2020
try {
2121
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
2222

23-
if (!authResult.success) {
23+
if (!authResult.success || !authResult.userId) {
2424
logger.warn(`[${requestId}] Unauthorized Teams chat write attempt: ${authResult.error}`)
2525
return NextResponse.json(
2626
{
@@ -31,10 +31,11 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
3131
)
3232
}
3333

34+
const userId = authResult.userId
3435
logger.info(
3536
`[${requestId}] Authenticated Teams chat write request via ${authResult.authType}`,
3637
{
37-
userId: authResult.userId,
38+
userId,
3839
}
3940
)
4041

@@ -53,6 +54,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
5354
accessToken: validatedData.accessToken,
5455
requestId,
5556
logger,
57+
userId,
5658
})
5759

5860
let messageContent = validatedData.content

apps/sim/app/api/tools/quiver/text-to-svg/route.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,16 +8,18 @@ import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
88
import type { RawFileInput } from '@/lib/uploads/utils/file-schemas'
99
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
1010
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
11+
import { assertToolFileAccess } from '@/app/api/files/authorization'
1112

1213
const logger = createLogger('QuiverTextToSvgAPI')
1314

1415
export const POST = withRouteHandler(async (request: NextRequest) => {
1516
const requestId = generateRequestId()
1617

1718
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
18-
if (!authResult.success) {
19+
if (!authResult.success || !authResult.userId) {
1920
return NextResponse.json({ success: false, error: 'Unauthorized' }, { status: 401 })
2021
}
22+
const userId = authResult.userId
2123

2224
try {
2325
const parsed = await parseRequest(
@@ -51,6 +53,13 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
5153
if (parsed && typeof parsed === 'object') {
5254
const userFiles = processFilesToUserFiles([parsed as RawFileInput], requestId, logger)
5355
if (userFiles.length > 0) {
56+
const denied = await assertToolFileAccess(
57+
userFiles[0].key,
58+
userId,
59+
requestId,
60+
logger
61+
)
62+
if (denied) return denied
5463
const buffer = await downloadFileFromStorage(userFiles[0], requestId, logger)
5564
apiReferences.push({ base64: buffer.toString('base64') })
5665
}
@@ -61,6 +70,8 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
6170
} else if (typeof ref === 'object' && ref !== null) {
6271
const userFiles = processFilesToUserFiles([ref as RawFileInput], requestId, logger)
6372
if (userFiles.length > 0) {
73+
const denied = await assertToolFileAccess(userFiles[0].key, userId, requestId, logger)
74+
if (denied) return denied
6475
const buffer = await downloadFileFromStorage(userFiles[0], requestId, logger)
6576
apiReferences.push({ base64: buffer.toString('base64') })
6677
}

apps/sim/app/api/tools/sap_concur/upload/route.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { generateRequestId } from '@/lib/core/utils/request'
88
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
99
import { processFilesToUserFiles, type RawFileInput } from '@/lib/uploads/utils/file-utils'
1010
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
11+
import { assertToolFileAccess } from '@/app/api/files/authorization'
1112
import {
1213
assertSafeExternalUrl,
1314
extractSapConcurError,
@@ -180,13 +181,14 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
180181

181182
try {
182183
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
183-
if (!authResult.success) {
184+
if (!authResult.success || !authResult.userId) {
184185
logger.warn(`[${requestId}] Unauthorized Concur upload request: ${authResult.error}`)
185186
return NextResponse.json(
186187
{ success: false, error: authResult.error || 'Authentication required' },
187188
{ status: 401 }
188189
)
189190
}
191+
const userId = authResult.userId
190192

191193
// boundary-raw-json: internal upload envelope validated by SapConcurUploadRequestSchema below; not a public boundary
192194
const json = await request.json()
@@ -204,6 +206,8 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
204206
)
205207
}
206208
const userFile = userFiles[0]
209+
const denied = await assertToolFileAccess(userFile.key, userId, requestId, logger)
210+
if (denied) return denied
207211
const fileBuffer = await downloadFileFromStorage(userFile, requestId, logger)
208212
const fileName = userFile.name
209213
const mimeType = inferMimeType(fileName, userFile.type)

apps/sim/app/api/tools/slack/send-message/route.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
1717
try {
1818
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
1919

20-
if (!authResult.success) {
20+
if (!authResult.success || !authResult.userId) {
2121
logger.warn(`[${requestId}] Unauthorized Slack send attempt: ${authResult.error}`)
2222
return NextResponse.json(
2323
{
@@ -28,8 +28,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
2828
)
2929
}
3030

31+
const userId = authResult.userId
3132
logger.info(`[${requestId}] Authenticated Slack send request via ${authResult.authType}`, {
32-
userId: authResult.userId,
33+
userId,
3334
})
3435

3536
const parsed = await parseRequest(slackSendMessageContract, request, {})
@@ -50,6 +51,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
5051
accessToken: validatedData.accessToken,
5152
channel: validatedData.channel ?? undefined,
5253
userId: validatedData.userId ?? undefined,
54+
ownerUserId: userId,
5355
text: validatedData.text,
5456
threadTs: validatedData.thread_ts ?? undefined,
5557
blocks: validatedData.blocks ?? undefined,

apps/sim/app/api/tools/slack/utils.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { Logger } from '@sim/logger'
22
import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server'
33
import { processFilesToUserFiles } from '@/lib/uploads/utils/file-utils'
44
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
5+
import { verifyFileAccess } from '@/app/api/files/authorization'
56
import type { ToolFileData } from '@/tools/types'
67

78
/**
@@ -73,7 +74,8 @@ async function uploadFilesToSlack(
7374
files: any[],
7475
accessToken: string,
7576
requestId: string,
76-
logger: Logger
77+
logger: Logger,
78+
ownerUserId: string
7779
): Promise<{ fileIds: string[]; files: ToolFileData[] }> {
7880
const userFiles = processFilesToUserFiles(files, requestId, logger)
7981
const uploadedFileIds: string[] = []
@@ -82,6 +84,11 @@ async function uploadFilesToSlack(
8284
for (const userFile of userFiles) {
8385
logger.info(`[${requestId}] Uploading file: ${userFile.name}`)
8486

87+
const hasAccess = await verifyFileAccess(userFile.key, ownerUserId)
88+
if (!hasAccess) {
89+
throw new Error('File not found')
90+
}
91+
8592
const buffer = await downloadFileFromStorage(userFile, requestId, logger)
8693

8794
const getUrlResponse = await fetch('https://slack.com/api/files.getUploadURLExternal', {
@@ -220,6 +227,7 @@ export interface SlackMessageParams {
220227
accessToken: string
221228
channel?: string
222229
userId?: string
230+
ownerUserId: string
223231
text: string
224232
threadTs?: string | null
225233
blocks?: unknown[] | null
@@ -245,7 +253,7 @@ export async function sendSlackMessage(
245253
}
246254
error?: string
247255
}> {
248-
const { accessToken, text, threadTs, blocks, files } = params
256+
const { accessToken, text, threadTs, blocks, files, ownerUserId } = params
249257
let { channel } = params
250258

251259
if (!channel && params.userId) {
@@ -278,7 +286,8 @@ export async function sendSlackMessage(
278286
files,
279287
accessToken,
280288
requestId,
281-
logger
289+
logger,
290+
ownerUserId
282291
)
283292

284293
// No valid files uploaded - send text-only

apps/sim/app/api/tools/stt/route.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ import {
1717
downloadFileFromStorage,
1818
resolveInternalFileUrl,
1919
} from '@/lib/uploads/utils/file-utils.server'
20+
import { assertToolFileAccess } from '@/app/api/files/authorization'
2021
import type { TranscriptSegment } from '@/tools/stt/types'
2122

2223
const logger = createLogger('SttProxyAPI')
@@ -31,7 +32,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
3132

3233
try {
3334
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
34-
if (!authResult.success) {
35+
if (!authResult.success || !authResult.userId) {
3536
return NextResponse.json({ error: 'Unauthorized' }, { status: 401 })
3637
}
3738

@@ -79,6 +80,8 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
7980
const file = Array.isArray(body.audioFile) ? body.audioFile[0] : body.audioFile
8081
logger.info(`[${requestId}] Processing uploaded file: ${file.name}`)
8182

83+
const deniedAudio = await assertToolFileAccess(file.key, userId, requestId, logger)
84+
if (deniedAudio) return deniedAudio
8285
audioBuffer = await downloadFileFromStorage(file, requestId, logger)
8386
audioFileName = file.name
8487
// file.type may be missing if the file came from a block that doesn't preserve it
@@ -97,6 +100,8 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
97100
: body.audioFileReference
98101
logger.info(`[${requestId}] Processing referenced file: ${file.name}`)
99102

103+
const deniedRef = await assertToolFileAccess(file.key, userId, requestId, logger)
104+
if (deniedRef) return deniedRef
100105
audioBuffer = await downloadFileFromStorage(file, requestId, logger)
101106
audioFileName = file.name
102107

apps/sim/app/api/tools/textract/parse/route.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ import {
1818
downloadFileFromStorage,
1919
resolveInternalFileUrl,
2020
} from '@/lib/uploads/utils/file-utils.server'
21+
import { assertToolFileAccess } from '@/app/api/files/authorization'
2122

2223
export const dynamic = 'force-dynamic'
2324
export const maxDuration = 300 // 5 minutes for large multi-page PDF processing
@@ -428,6 +429,8 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
428429
)
429430
}
430431

432+
const denied = await assertToolFileAccess(userFile.key, userId, requestId, logger)
433+
if (denied) return denied
431434
const buffer = await downloadFileFromStorage(userFile, requestId, logger)
432435
bytes = buffer.toString('base64')
433436
contentType = userFile.type || 'application/octet-stream'

apps/sim/tools/microsoft_teams/server-utils.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import type { Logger } from '@sim/logger'
77
import { secureFetchWithValidation } from '@/lib/core/security/input-validation.server'
88
import { processFilesToUserFiles, type RawFileInput } from '@/lib/uploads/utils/file-utils'
99
import { downloadFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
10+
import { verifyFileAccess } from '@/app/api/files/authorization'
1011
import type { UserFile } from '@/executor/types'
1112
import type { GraphApiErrorResponse, GraphDriveItem } from '@/tools/microsoft_teams/types'
1213

@@ -45,8 +46,9 @@ export async function uploadFilesForTeamsMessage(params: {
4546
accessToken: string
4647
requestId: string
4748
logger: Logger
49+
userId: string
4850
}): Promise<TeamsFileUploadResult> {
49-
const { rawFiles, accessToken, requestId, logger: log } = params
51+
const { rawFiles, accessToken, requestId, logger: log, userId } = params
5052
const attachments: TeamsAttachmentRef[] = []
5153
const filesOutput: TeamsFileOutput[] = []
5254

@@ -72,6 +74,11 @@ export async function uploadFilesForTeamsMessage(params: {
7274

7375
log.info(`[${requestId}] Uploading file to Teams: ${file.name} (${file.size} bytes)`)
7476

77+
const hasAccess = await verifyFileAccess(file.key, userId)
78+
if (!hasAccess) {
79+
throw new Error('File not found')
80+
}
81+
7582
// Download file from storage
7683
const buffer = await downloadFileFromStorage(file, requestId, log)
7784
filesOutput.push({

0 commit comments

Comments
 (0)