Skip to content

Commit e168fab

Browse files
committed
fix(security): remove localhost CORS origin, consolidate CORS in proxy
Move all /api/* CORS handling from next.config.ts to proxy.ts so the runtime can resolve allowed origin per-request instead of baking it at build time (which produced "Access-Control-Allow-Origin: http://localhost:3000" with credentials:true in production). - proxy.ts: per-route CORS policy table covering auth, MCP, form, and workflow execute endpoints; OPTIONS preflight short-circuit; Vary: Origin when origin is not '*'; form routes defer to route handler's addCorsHeaders to avoid double-setting - next.config.ts: drop all /api/* Access-Control-Allow-* headers; keep COEP/COOP/CSP - deployment.ts: addCorsHeaders sets Vary: Origin alongside reflected Allow-Origin - Dockerfile: drop NEXT_PUBLIC_APP_URL build placeholder (Zod has skipValidation:true; build path doesn't read it) - Remove 8 dead OPTIONS handlers and their preflight tests now that the proxy handles preflight uniformly
1 parent 6827be7 commit e168fab

16 files changed

Lines changed: 113 additions & 224 deletions

File tree

apps/sim/app/api/files/delete/route.test.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ vi.mock('fs/promises', () => ({
9191
}))
9292

9393
import { createMockRequest } from '@sim/testing'
94-
import { OPTIONS, POST } from '@/app/api/files/delete/route'
94+
import { POST } from '@/app/api/files/delete/route'
9595

9696
describe('File Delete API Route', () => {
9797
beforeEach(() => {
@@ -198,12 +198,4 @@ describe('File Delete API Route', () => {
198198
expect(data).toHaveProperty('error', 'InvalidRequestError')
199199
expect(data).toHaveProperty('message', 'No file path provided')
200200
})
201-
202-
it('should handle CORS preflight requests', async () => {
203-
const response = await OPTIONS()
204-
205-
expect(response.status).toBe(204)
206-
expect(response.headers.get('Access-Control-Allow-Methods')).toBe('GET, POST, DELETE, OPTIONS')
207-
expect(response.headers.get('Access-Control-Allow-Headers')).toBe('Content-Type')
208-
})
209201
})

apps/sim/app/api/files/delete/route.ts

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import { extractStorageKey, inferContextFromKey } from '@/lib/uploads/utils/file
1212
import { verifyFileAccess } from '@/app/api/files/authorization'
1313
import {
1414
createErrorResponse,
15-
createOptionsResponse,
1615
createSuccessResponse,
1716
extractFilename,
1817
FileNotFoundError,
@@ -119,10 +118,3 @@ function extractStorageKeyFromPath(filePath: string): string {
119118

120119
return extractFilename(filePath)
121120
}
122-
123-
/**
124-
* Handle CORS preflight requests
125-
*/
126-
export const OPTIONS = withRouteHandler(async () => {
127-
return createOptionsResponse()
128-
})

apps/sim/app/api/files/presigned/batch/route.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -156,17 +156,3 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
156156
)
157157
}
158158
})
159-
160-
export const OPTIONS = withRouteHandler(async () => {
161-
return NextResponse.json(
162-
{},
163-
{
164-
status: 200,
165-
headers: {
166-
'Access-Control-Allow-Origin': '*',
167-
'Access-Control-Allow-Methods': 'POST, OPTIONS',
168-
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
169-
},
170-
}
171-
)
172-
})

apps/sim/app/api/files/presigned/route.test.ts

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ vi.mock('@/lib/uploads', () => ({
107107
isUsingCloudStorage: mockIsUsingCloudStorageUploads,
108108
}))
109109

110-
import { OPTIONS, POST } from '@/app/api/files/presigned/route'
110+
import { POST } from '@/app/api/files/presigned/route'
111111

112112
const defaultMockUser = {
113113
id: 'test-user-id',
@@ -827,16 +827,4 @@ describe('/api/files/presigned', () => {
827827
expect(mockValidateAttachmentFileType).not.toHaveBeenCalled()
828828
})
829829
})
830-
831-
describe('OPTIONS', () => {
832-
it('should handle CORS preflight requests', async () => {
833-
const response = await OPTIONS()
834-
835-
expect(response.status).toBe(200)
836-
expect(response.headers.get('Access-Control-Allow-Methods')).toBe('POST, OPTIONS')
837-
expect(response.headers.get('Access-Control-Allow-Headers')).toBe(
838-
'Content-Type, Authorization'
839-
)
840-
})
841-
})
842830
})

apps/sim/app/api/files/presigned/route.ts

Lines changed: 0 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -310,17 +310,3 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
310310
)
311311
}
312312
})
313-
314-
export const OPTIONS = withRouteHandler(async () => {
315-
return NextResponse.json(
316-
{},
317-
{
318-
status: 200,
319-
headers: {
320-
'Access-Control-Allow-Origin': '*',
321-
'Access-Control-Allow-Methods': 'POST, OPTIONS',
322-
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
323-
},
324-
}
325-
)
326-
})

apps/sim/app/api/files/upload/route.test.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,7 @@ vi.mock('@/lib/uploads/setup.server', () => ({
9595
}))
9696

9797
import { uploadWorkspaceFile } from '@/lib/uploads/contexts/workspace'
98-
import { OPTIONS, POST } from '@/app/api/files/upload/route'
98+
import { POST } from '@/app/api/files/upload/route'
9999

100100
/**
101101
* Configure mocks for authenticated file upload tests
@@ -307,14 +307,6 @@ describe('File Upload API Route', () => {
307307
expect(data).toHaveProperty('error')
308308
expect(typeof data.error).toBe('string')
309309
})
310-
311-
it('should handle CORS preflight requests', async () => {
312-
const response = await OPTIONS()
313-
314-
expect(response.status).toBe(204)
315-
expect(response.headers.get('Access-Control-Allow-Methods')).toBe('GET, POST, DELETE, OPTIONS')
316-
expect(response.headers.get('Access-Control-Allow-Headers')).toBe('Content-Type')
317-
})
318310
})
319311

320312
describe('File Upload Security Tests', () => {

apps/sim/app/api/files/upload/route.ts

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,7 @@ import {
2121
validateFileType,
2222
} from '@/lib/uploads/utils/validation'
2323
import { getUserEntityPermissions } from '@/lib/workspaces/permissions/utils'
24-
import {
25-
createErrorResponse,
26-
createOptionsResponse,
27-
InvalidRequestError,
28-
} from '@/app/api/files/utils'
24+
import { createErrorResponse, InvalidRequestError } from '@/app/api/files/utils'
2925

3026
const ALLOWED_EXTENSIONS = new Set<string>(SUPPORTED_ATTACHMENT_EXTENSIONS)
3127

@@ -430,7 +426,3 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
430426
return createErrorResponse(error instanceof Error ? error : new Error('File upload failed'))
431427
}
432428
})
433-
434-
export const OPTIONS = withRouteHandler(async () => {
435-
return createOptionsResponse()
436-
})

apps/sim/app/api/files/utils.ts

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -238,13 +238,3 @@ export function createErrorResponse(error: Error, status = 500): NextResponse {
238238
export function createSuccessResponse(data: ApiSuccessResponse): NextResponse {
239239
return NextResponse.json(data)
240240
}
241-
242-
export function createOptionsResponse(): NextResponse {
243-
return new NextResponse(null, {
244-
status: 204,
245-
headers: {
246-
'Access-Control-Allow-Methods': 'GET, POST, DELETE, OPTIONS',
247-
'Access-Control-Allow-Headers': 'Content-Type',
248-
},
249-
})
250-
}

apps/sim/app/api/form/[identifier]/route.ts

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -401,7 +401,3 @@ export const GET = withRouteHandler(
401401
}
402402
}
403403
)
404-
405-
export const OPTIONS = withRouteHandler(async (request: NextRequest) => {
406-
return addCorsHeaders(new NextResponse(null, { status: 204 }), request)
407-
})

apps/sim/app/api/mcp/copilot/route.ts

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -386,19 +386,6 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
386386
}
387387
})
388388

389-
export const OPTIONS = withRouteHandler(async () => {
390-
return new NextResponse(null, {
391-
status: 204,
392-
headers: {
393-
'Access-Control-Allow-Origin': '*',
394-
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS, DELETE',
395-
'Access-Control-Allow-Headers':
396-
'Content-Type, Authorization, X-API-Key, X-Requested-With, Accept',
397-
'Access-Control-Max-Age': '86400',
398-
},
399-
})
400-
})
401-
402389
export const DELETE = withRouteHandler(async (request: NextRequest) => {
403390
void request
404391
return NextResponse.json(createError(0, -32000, 'Method not allowed.'), { status: 405 })

0 commit comments

Comments
 (0)