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
6 changes: 3 additions & 3 deletions packages/server/src/utils/XSS.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Request, Response, NextFunction } from 'express'
import sanitizeHtml from 'sanitize-html'
import { isPredictionRequest, extractChatflowId, validateChatflowDomain } from './domainValidation'
import { extractChatflowId, validateChatflowDomain, isPublicChatflowRequest } from './domainValidation'

export function sanitizeMiddleware(req: Request, res: Response, next: NextFunction): void {
// decoding is necessary as the url is encoded by the browser
Expand Down Expand Up @@ -43,7 +43,7 @@ export function getCorsOptions(): any {
const corsOptions = {
origin: async (origin: string | undefined, originCallback: (err: Error | null, allow?: boolean) => void) => {
const allowedOrigins = getAllowedCorsOrigins()
const isPredictionReq = isPredictionRequest(req.url)
const isPublicChatflowReq = isPublicChatflowRequest(req.url)
const allowedList = parseAllowedOrigins(allowedOrigins)
const originLc = origin?.toLowerCase()

Expand All @@ -53,7 +53,7 @@ export function getCorsOptions(): any {
// Global allow: '*' or exact match
const globallyAllowed = allowedOrigins === '*' || allowedList.includes(originLc)

if (isPredictionReq) {
if (isPublicChatflowReq) {
// Per-chatflow allowlist OR globally allowed
const chatflowId = extractChatflowId(req.url)
let chatflowAllowed = false
Expand Down
39 changes: 31 additions & 8 deletions packages/server/src/utils/domainValidation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@ import { isValidUUID } from 'flowise-components'
import chatflowsService from '../services/chatflows'
import logger from './logger'

// List of allowed URL slugs for public access to chatbots
// It assumes the URL format includes one of the following patterns:
// /prediction/{chatflowId}.
// /public-chatbotConfig/{chatflowId}
// /chatflows-streaming/{chatflowId}
const ALLOWED_SLUGS = ['/prediction/', '/public-chatbotConfig/', '/chatflows-streaming/']

/**
* Validates if the origin is allowed for a specific chatflow
* @param chatflowId - The chatflow ID to validate against
Expand Down Expand Up @@ -58,10 +65,12 @@ async function validateChatflowDomain(chatflowId: string, origin: string, worksp
function extractChatflowId(url: string): string | null {
try {
const urlParts = url.split('/')
const predictionIndex = urlParts.indexOf('prediction')
const slug = extractSlugFromUrl(url)
if (!slug) return null
const slugIndex = urlParts.indexOf(slug)

if (predictionIndex !== -1 && urlParts.length > predictionIndex + 1) {
const chatflowId = urlParts[predictionIndex + 1]
if (slugIndex !== -1 && urlParts.length > slugIndex + 1) {
const chatflowId = urlParts[slugIndex + 1]
// Remove query parameters if present
return chatflowId.split('?')[0]
}
Expand All @@ -74,12 +83,26 @@ function extractChatflowId(url: string): string | null {
}

/**
* Validates if a request is a prediction request
* Extracts the slug from the URL if it matches any of the allowed slugs
* @param url - The request URL
* @returns string | null - The matched slug or null if no match
*/
function extractSlugFromUrl(url: string): string | null {
for (const publicUrl of ALLOWED_SLUGS) {
if (url.includes(publicUrl)) {
return publicUrl.replace(/\//g, '') // remove slashes
}
}
return null
}

/**
* Validates if a request is for public chatflows (embedded chatbots)
* @param url - The request URL
* @returns boolean - True if it's a prediction request
* @returns boolean - True if it's a public chatflow request
*/
function isPredictionRequest(url: string): boolean {
return url.includes('/prediction/')
function isPublicChatflowRequest(url: string): boolean {
return extractSlugFromUrl(url) !== null
}

/**
Expand All @@ -106,4 +129,4 @@ async function getUnauthorizedOriginError(chatflowId: string, workspaceId?: stri
}
}

export { isPredictionRequest, extractChatflowId, validateChatflowDomain, getUnauthorizedOriginError }
export { isPublicChatflowRequest, extractChatflowId, validateChatflowDomain, getUnauthorizedOriginError }
Loading