Merge pull request #1608 from automaton82/FEATURE-CORS-CSP-headers-security

Implementing CORS and CSP headers from config
This commit is contained in:
Henry Heng
2024-01-29 10:04:19 +00:00
committed by GitHub
7 changed files with 54 additions and 6 deletions
+2
View File
@@ -123,6 +123,8 @@ Flowise support different environment variables to configure your instance. You
| Variable | Description | Type | Default | | Variable | Description | Type | Default |
| --------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------ | ----------------------------------- | | --------------------------- | ---------------------------------------------------------------------------- | ------------------------------------------------ | ----------------------------------- |
| PORT | The HTTP port Flowise runs on | Number | 3000 | | 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_USERNAME | Username to login | String | |
| FLOWISE_PASSWORD | Password to login | String | | | FLOWISE_PASSWORD | Password to login | String | |
| DEBUG | Print logs from components | Boolean | | | DEBUG | Print logs from components | Boolean | |
+3
View File
@@ -4,6 +4,9 @@ APIKEY_PATH=/root/.flowise
SECRETKEY_PATH=/root/.flowise SECRETKEY_PATH=/root/.flowise
LOG_PATH=/root/.flowise/logs LOG_PATH=/root/.flowise/logs
# CORS_ORIGINS="*"
# IFRAME_ORIGINS="*"
# NUMBER_OF_PROXIES= 1 # NUMBER_OF_PROXIES= 1
# DATABASE_TYPE=postgres # DATABASE_TYPE=postgres
+2
View File
@@ -6,6 +6,8 @@ services:
restart: always restart: always
environment: environment:
- PORT=${PORT} - PORT=${PORT}
- CORS_ORIGINS=${CORS_ORIGINS}
- IFRAME_ORIGINS=${IFRAME_ORIGINS}
- FLOWISE_USERNAME=${FLOWISE_USERNAME} - FLOWISE_USERNAME=${FLOWISE_USERNAME}
- FLOWISE_PASSWORD=${FLOWISE_PASSWORD} - FLOWISE_PASSWORD=${FLOWISE_PASSWORD}
- DEBUG=${DEBUG} - DEBUG=${DEBUG}
+2
View File
@@ -1,4 +1,6 @@
PORT=3000 PORT=3000
# CORS_ORIGINS="*"
# IFRAME_ORIGINS="*"
# DATABASE_PATH=/your_database_path/.flowise # DATABASE_PATH=/your_database_path/.flowise
# APIKEY_PATH=/your_api_key_path/.flowise # APIKEY_PATH=/your_api_key_path/.flowise
# SECRETKEY_PATH=/your_api_key_path/.flowise # SECRETKEY_PATH=/your_api_key_path/.flowise
+4
View File
@@ -19,6 +19,8 @@ export default class Start extends Command {
FLOWISE_USERNAME: Flags.string(), FLOWISE_USERNAME: Flags.string(),
FLOWISE_PASSWORD: Flags.string(), FLOWISE_PASSWORD: Flags.string(),
PORT: Flags.string(), PORT: Flags.string(),
CORS_ORIGINS: Flags.string(),
IFRAME_ORIGINS: Flags.string(),
DEBUG: Flags.string(), DEBUG: Flags.string(),
APIKEY_PATH: Flags.string(), APIKEY_PATH: Flags.string(),
SECRETKEY_PATH: Flags.string(), SECRETKEY_PATH: Flags.string(),
@@ -78,6 +80,8 @@ export default class Start extends Command {
const { flags } = await this.parse(Start) const { flags } = await this.parse(Start)
if (flags.PORT) process.env.PORT = flags.PORT 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.DEBUG) process.env.DEBUG = flags.DEBUG
if (flags.NUMBER_OF_PROXIES) process.env.NUMBER_OF_PROXIES = flags.NUMBER_OF_PROXIES if (flags.NUMBER_OF_PROXIES) process.env.NUMBER_OF_PROXIES = flags.NUMBER_OF_PROXIES
+16 -6
View File
@@ -62,7 +62,7 @@ import { CachePool } from './CachePool'
import { ICommonObject, IMessage, INodeOptionsValue, handleEscapeCharacters, webCrawl, xmlScrape } from 'flowise-components' import { ICommonObject, IMessage, INodeOptionsValue, handleEscapeCharacters, webCrawl, xmlScrape } from 'flowise-components'
import { createRateLimiter, getRateLimiter, initializeRateLimiter } from './utils/rateLimit' import { createRateLimiter, getRateLimiter, initializeRateLimiter } from './utils/rateLimit'
import { addAPIKey, compareKeys, deleteAPIKey, getApiKey, getAPIKeys, updateAPIKey } from './utils/apiKey' 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 axios from 'axios'
import { Client } from 'langchainhub' import { Client } from 'langchainhub'
import { parsePrompt } from './utils/hub' 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) if (process.env.NUMBER_OF_PROXIES && parseInt(process.env.NUMBER_OF_PROXIES) > 0)
this.app.set('trust proxy', parseInt(process.env.NUMBER_OF_PROXIES)) this.app.set('trust proxy', parseInt(process.env.NUMBER_OF_PROXIES))
// Allow access from * // Allow access from specified domains
this.app.use(cors()) 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 // Switch off the default 'X-Powered-By: Express' header
this.app.disable('x-powered-by') this.app.disable('x-powered-by')
@@ -1883,9 +1895,7 @@ export async function start(): Promise<void> {
const server = http.createServer(serverApp.app) const server = http.createServer(serverApp.app)
const io = new Server(server, { const io = new Server(server, {
cors: { cors: getCorsOptions()
origin: '*'
}
}) })
await serverApp.initDatabase() await serverApp.initDatabase()
+25
View File
@@ -18,3 +18,28 @@ export function sanitizeMiddleware(req: Request, res: Response, next: NextFuncti
} }
next() 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 ?? '*'
}