From 3be2393412fd0866b9f3bb0759ffeaff69253311 Mon Sep 17 00:00:00 2001 From: automaton82 Date: Thu, 25 Jan 2024 11:39:13 -0500 Subject: [PATCH 1/3] Implementing CORS and CSP headers from env config --- packages/server/.env.example | 2 ++ packages/server/src/index.ts | 37 ++++++++++++++++++++++++++++---- packages/server/src/utils/XSS.ts | 11 ++++++++++ 3 files changed, 46 insertions(+), 4 deletions(-) diff --git a/packages/server/.env.example b/packages/server/.env.example index ed54ac66..8b5dbe8f 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -1,4 +1,6 @@ PORT=3000 +# CORS_ORIGINS="*" +# EMBEDDING_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/index.ts b/packages/server/src/index.ts index 6d4c1887..14685aee 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 } 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, getAllowedCorsOrigins, getAllowedEmbeddingOrigins } from './utils/XSS' import axios from 'axios' import { Client } from 'langchainhub' import { parsePrompt } from './utils/hub' @@ -126,8 +126,30 @@ 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 + 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(new Error('Not allowed by CORS')) + } + } + } + this.app.use(cors(corsOptions)) + + // Allow embedding from specified domains. + this.app.use((req, res, next) => { + const allowedOrigins = getAllowedEmbeddingOrigins() + 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') @@ -1863,7 +1885,14 @@ export async function start(): Promise { const io = new Server(server, { cors: { - origin: '*' + 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(new Error('Not allowed by CORS')) + } + } } }) diff --git a/packages/server/src/utils/XSS.ts b/packages/server/src/utils/XSS.ts index 5d8b81e9..a9ad9278 100644 --- a/packages/server/src/utils/XSS.ts +++ b/packages/server/src/utils/XSS.ts @@ -18,3 +18,14 @@ 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 getAllowedEmbeddingOrigins(): string { + // Expects FQDN separated by commas, otherwise nothing or * for all. + // Also CSP allowed values: self or none + return process.env.EMBEDDING_ORIGINS ?? '*' +} From 657dace89e9d7b6089de6f01e76608724e4aac4a Mon Sep 17 00:00:00 2001 From: automaton82 Date: Thu, 25 Jan 2024 15:29:02 -0500 Subject: [PATCH 2/3] Fixing comments from PR --- CONTRIBUTING.md | 2 ++ docker/.env.example | 3 +++ docker/docker-compose.yml | 2 ++ packages/server/.env.example | 2 +- packages/server/src/commands/start.ts | 4 ++++ packages/server/src/index.ts | 27 ++++----------------------- packages/server/src/utils/XSS.ts | 18 ++++++++++++++++-- 7 files changed, 32 insertions(+), 26 deletions(-) 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 8b5dbe8f..5f1b1848 100644 --- a/packages/server/.env.example +++ b/packages/server/.env.example @@ -1,6 +1,6 @@ PORT=3000 # CORS_ORIGINS="*" -# EMBEDDING_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 14685aee..adc3b0c6 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 } from 'flowise-components' import { createRateLimiter, getRateLimiter, initializeRateLimiter } from './utils/rateLimit' import { addAPIKey, compareKeys, deleteAPIKey, getApiKey, getAPIKeys, updateAPIKey } from './utils/apiKey' -import { sanitizeMiddleware, getAllowedCorsOrigins, getAllowedEmbeddingOrigins } from './utils/XSS' +import { sanitizeMiddleware, getCorsOptions, getAllowedIframeOrigins } from './utils/XSS' import axios from 'axios' import { Client } from 'langchainhub' import { parsePrompt } from './utils/hub' @@ -127,21 +127,11 @@ export class App { this.app.set('trust proxy', parseInt(process.env.NUMBER_OF_PROXIES)) // Allow access from specified domains - 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(new Error('Not allowed by CORS')) - } - } - } - this.app.use(cors(corsOptions)) + this.app.use(cors(getCorsOptions())) // Allow embedding from specified domains. this.app.use((req, res, next) => { - const allowedOrigins = getAllowedEmbeddingOrigins() + const allowedOrigins = getAllowedIframeOrigins() if (allowedOrigins == '*') { next() } else { @@ -1884,16 +1874,7 @@ export async function start(): Promise { const server = http.createServer(serverApp.app) const io = new Server(server, { - cors: { - 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(new Error('Not allowed by CORS')) - } - } - } + cors: getCorsOptions() }) await serverApp.initDatabase() diff --git a/packages/server/src/utils/XSS.ts b/packages/server/src/utils/XSS.ts index a9ad9278..d4acef49 100644 --- a/packages/server/src/utils/XSS.ts +++ b/packages/server/src/utils/XSS.ts @@ -24,8 +24,22 @@ export function getAllowedCorsOrigins(): string { return process.env.CORS_ORIGINS ?? '*' } -export function getAllowedEmbeddingOrigins(): string { +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(new Error('Not allowed by CORS')) + } + } + } + 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.EMBEDDING_ORIGINS ?? '*' + return process.env.IFRAME_ORIGINS ?? '*' } From 089928aaa89766e99115e936f3704d3bae14a90d Mon Sep 17 00:00:00 2001 From: automaton82 Date: Thu, 25 Jan 2024 15:43:42 -0500 Subject: [PATCH 3/3] Return no result with disallowed for CORS response, not an error --- packages/server/src/utils/XSS.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/server/src/utils/XSS.ts b/packages/server/src/utils/XSS.ts index d4acef49..96bbab57 100644 --- a/packages/server/src/utils/XSS.ts +++ b/packages/server/src/utils/XSS.ts @@ -31,7 +31,7 @@ export function getCorsOptions(): any { if (!origin || allowedOrigins == '*' || allowedOrigins.indexOf(origin) !== -1) { callback(null, true) } else { - callback(new Error('Not allowed by CORS')) + callback(null, false) } } }