Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 10 additions & 4 deletions apps/sim/app/api/proxy/image/route.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { type NextRequest, NextResponse } from 'next/server'
import { createLogger } from '@/lib/logs/console/logger'
import { validateImageUrl } from '@/lib/security/url-validation'

const logger = createLogger('ImageProxyAPI')

Expand All @@ -17,10 +18,18 @@ export async function GET(request: NextRequest) {
return new NextResponse('Missing URL parameter', { status: 400 })
}

const urlValidation = validateImageUrl(imageUrl)
if (!urlValidation.isValid) {
logger.warn(`[${requestId}] Blocked image proxy request`, {
url: imageUrl.substring(0, 100),
error: urlValidation.error,
})
return new NextResponse(urlValidation.error || 'Invalid image URL', { status: 403 })
}

logger.info(`[${requestId}] Proxying image request for: ${imageUrl}`)

try {
// Use fetch with custom headers that appear more browser-like
const imageResponse = await fetch(imageUrl, {
headers: {
'User-Agent':
Expand All @@ -45,18 +54,15 @@ export async function GET(request: NextRequest) {
})
}

// Get image content type from response headers
const contentType = imageResponse.headers.get('content-type') || 'image/jpeg'

// Get the image as a blob
const imageBlob = await imageResponse.blob()

if (imageBlob.size === 0) {
logger.error(`[${requestId}] Empty image blob received`)
return new NextResponse('Empty image received', { status: 404 })
}

// Return the image with appropriate headers
return new NextResponse(imageBlob, {
headers: {
'Content-Type': contentType,
Expand Down
20 changes: 10 additions & 10 deletions apps/sim/app/api/proxy/route.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { NextResponse } from 'next/server'
import { isDev } from '@/lib/environment'
import { createLogger } from '@/lib/logs/console/logger'
import { validateProxyUrl } from '@/lib/security/url-validation'
import { executeTool } from '@/tools'
import { getTool, validateRequiredParametersAfterMerge } from '@/tools/utils'

Expand Down Expand Up @@ -80,6 +81,15 @@ export async function GET(request: Request) {
return createErrorResponse("Missing 'url' parameter", 400)
}

const urlValidation = validateProxyUrl(targetUrl)
if (!urlValidation.isValid) {
logger.warn(`[${requestId}] Blocked proxy request`, {
url: targetUrl.substring(0, 100),
error: urlValidation.error,
})
return createErrorResponse(urlValidation.error || 'Invalid URL', 403)
}

const method = url.searchParams.get('method') || 'GET'

const bodyParam = url.searchParams.get('body')
Expand Down Expand Up @@ -109,7 +119,6 @@ export async function GET(request: Request) {
logger.info(`[${requestId}] Proxying ${method} request to: ${targetUrl}`)

try {
// Forward the request to the target URL with all specified headers
const response = await fetch(targetUrl, {
method: method,
headers: {
Expand All @@ -119,7 +128,6 @@ export async function GET(request: Request) {
body: body || undefined,
})

// Get response data
const contentType = response.headers.get('content-type') || ''
let data

Expand All @@ -129,7 +137,6 @@ export async function GET(request: Request) {
data = await response.text()
}

// For error responses, include a more descriptive error message
const errorMessage = !response.ok
? data && typeof data === 'object' && data.error
? `${data.error.message || JSON.stringify(data.error)}`
Expand All @@ -140,7 +147,6 @@ export async function GET(request: Request) {
logger.error(`[${requestId}] External API error: ${response.status} ${response.statusText}`)
}

// Return the proxied response
return formatResponse({
success: response.ok,
status: response.status,
Expand All @@ -166,7 +172,6 @@ export async function POST(request: Request) {
const startTimeISO = startTime.toISOString()

try {
// Parse request body
let requestBody
try {
requestBody = await request.json()
Expand All @@ -186,23 +191,20 @@ export async function POST(request: Request) {

logger.info(`[${requestId}] Processing tool: ${toolId}`)

// Get tool
const tool = getTool(toolId)

if (!tool) {
logger.error(`[${requestId}] Tool not found: ${toolId}`)
throw new Error(`Tool not found: ${toolId}`)
}

// Validate the tool and its parameters
try {
validateRequiredParametersAfterMerge(toolId, tool, params)
} catch (validationError) {
logger.warn(`[${requestId}] Tool validation failed for ${toolId}`, {
error: validationError instanceof Error ? validationError.message : String(validationError),
})

// Add timing information even to error responses
const endTime = new Date()
const endTimeISO = endTime.toISOString()
const duration = endTime.getTime() - startTime.getTime()
Expand All @@ -214,14 +216,12 @@ export async function POST(request: Request) {
})
}

// Check if tool has file outputs - if so, don't skip post-processing
const hasFileOutputs =
tool.outputs &&
Object.values(tool.outputs).some(
(output) => output.type === 'file' || output.type === 'file[]'
)

// Execute tool
const result = await executeTool(
toolId,
params,
Expand Down
Loading