diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 88d1aaac..747c0f39 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -123,6 +123,8 @@ Flowise support different environment variables to configure your instance. You | Variable | Description | Type | Default | | --------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------ | ----------------------------------- | | PORT | The HTTP port Flowise runs on | Number | 3000 | +| CORS_ORIGINS | The allowed origins for all cross-origin HTTP calls | String | | +| IFRAME_ORIGINS | The allowed origins for iframe src embedding | String | | | FLOWISE_USERNAME | Username to login | String | | | FLOWISE_PASSWORD | Password to login | String | | | DEBUG | Print logs from components | Boolean | | diff --git a/docker/.env.example b/docker/.env.example index 0fe69dd1..318ce864 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -4,6 +4,9 @@ APIKEY_PATH=/root/.flowise SECRETKEY_PATH=/root/.flowise LOG_PATH=/root/.flowise/logs +# CORS_ORIGINS="*" +# IFRAME_ORIGINS="*" + # NUMBER_OF_PROXIES= 1 # DATABASE_TYPE=postgres diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index c8c88bf3..24cbb22e 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -6,6 +6,8 @@ services: restart: always environment: - PORT=${PORT} + - CORS_ORIGINS=${CORS_ORIGINS} + - IFRAME_ORIGINS=${IFRAME_ORIGINS} - FLOWISE_USERNAME=${FLOWISE_USERNAME} - FLOWISE_PASSWORD=${FLOWISE_PASSWORD} - DEBUG=${DEBUG} diff --git a/packages/server/.env.example b/packages/server/.env.example index ed54ac66..5f1b1848 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -1,4 +1,6 @@ PORT=3000 +# CORS_ORIGINS="*" +# IFRAME_ORIGINS="*" # DATABASE_PATH=/your_database_path/.flowise # APIKEY_PATH=/your_api_key_path/.flowise # SECRETKEY_PATH=/your_api_key_path/.flowise diff --git a/packages/server/src/commands/start.ts b/packages/server/src/commands/start.ts index 08cd8298..013cab2d 100644 --- a/packages/server/src/commands/start.ts +++ b/packages/server/src/commands/start.ts @@ -19,6 +19,8 @@ export default class Start extends Command { FLOWISE_USERNAME: Flags.string(), FLOWISE_PASSWORD: Flags.string(), PORT: Flags.string(), + CORS_ORIGINS: Flags.string(), + IFRAME_ORIGINS: Flags.string(), DEBUG: Flags.string(), APIKEY_PATH: Flags.string(), SECRETKEY_PATH: Flags.string(), @@ -78,6 +80,8 @@ export default class Start extends Command { const { flags } = await this.parse(Start) if (flags.PORT) process.env.PORT = flags.PORT + if (flags.CORS_ORIGINS) process.env.CORS_ORIGINS = flags.CORS_ORIGINS + if (flags.IFRAME_ORIGINS) process.env.IFRAME_ORIGINS = flags.IFRAME_ORIGINS if (flags.DEBUG) process.env.DEBUG = flags.DEBUG if (flags.NUMBER_OF_PROXIES) process.env.NUMBER_OF_PROXIES = flags.NUMBER_OF_PROXIES diff --git a/packages/server/src/index.ts b/packages/server/src/index.ts index b8ebd5f7..b0bb06f5 100644 --- a/packages/server/src/index.ts +++ b/packages/server/src/index.ts @@ -62,7 +62,7 @@ import { CachePool } from './CachePool' import { ICommonObject, IMessage, INodeOptionsValue, handleEscapeCharacters, webCrawl, xmlScrape } from 'flowise-components' import { createRateLimiter, getRateLimiter, initializeRateLimiter } from './utils/rateLimit' import { addAPIKey, compareKeys, deleteAPIKey, getApiKey, getAPIKeys, updateAPIKey } from './utils/apiKey' -import { sanitizeMiddleware } from './utils/XSS' +import { sanitizeMiddleware, getCorsOptions, getAllowedIframeOrigins } from './utils/XSS' import axios from 'axios' import { Client } from 'langchainhub' import { parsePrompt } from './utils/hub' @@ -126,8 +126,20 @@ export class App { if (process.env.NUMBER_OF_PROXIES && parseInt(process.env.NUMBER_OF_PROXIES) > 0) this.app.set('trust proxy', parseInt(process.env.NUMBER_OF_PROXIES)) - // Allow access from * - this.app.use(cors()) + // Allow access from specified domains + this.app.use(cors(getCorsOptions())) + + // Allow embedding from specified domains. + this.app.use((req, res, next) => { + const allowedOrigins = getAllowedIframeOrigins() + if (allowedOrigins == '*') { + next() + } else { + const csp = `frame-ancestors ${allowedOrigins}` + res.setHeader('Content-Security-Policy', csp) + next() + } + }) // Switch off the default 'X-Powered-By: Express' header this.app.disable('x-powered-by') @@ -1883,9 +1895,7 @@ export async function start(): Promise { const server = http.createServer(serverApp.app) const io = new Server(server, { - cors: { - origin: '*' - } + cors: getCorsOptions() }) await serverApp.initDatabase() diff --git a/packages/server/src/utils/XSS.ts b/packages/server/src/utils/XSS.ts index 5d8b81e9..96bbab57 100644 --- a/packages/server/src/utils/XSS.ts +++ b/packages/server/src/utils/XSS.ts @@ -18,3 +18,28 @@ export function sanitizeMiddleware(req: Request, res: Response, next: NextFuncti } next() } + +export function getAllowedCorsOrigins(): string { + // Expects FQDN separated by commas, otherwise nothing or * for all. + return process.env.CORS_ORIGINS ?? '*' +} + +export function getCorsOptions(): any { + const corsOptions = { + origin: function (origin: string | undefined, callback: (err: Error | null, allow?: boolean) => void) { + const allowedOrigins = getAllowedCorsOrigins() + if (!origin || allowedOrigins == '*' || allowedOrigins.indexOf(origin) !== -1) { + callback(null, true) + } else { + callback(null, false) + } + } + } + return corsOptions +} + +export function getAllowedIframeOrigins(): string { + // Expects FQDN separated by commas, otherwise nothing or * for all. + // Also CSP allowed values: self or none + return process.env.IFRAME_ORIGINS ?? '*' +}