Skip to content
Open
Show file tree
Hide file tree
Changes from 3 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
2 changes: 1 addition & 1 deletion docker/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ PORT=3000
# LOG_SANITIZE_BODY_FIELDS=password,pwd,pass,secret,token,apikey,api_key,accesstoken,access_token,refreshtoken,refresh_token,clientsecret,client_secret,privatekey,private_key,secretkey,secret_key,auth,authorization,credential,credentials
# LOG_SANITIZE_HEADER_FIELDS=authorization,x-api-key,x-auth-token,cookie
# TOOL_FUNCTION_BUILTIN_DEP=crypto,fs
# TOOL_FUNCTION_EXTERNAL_DEP=moment,lodash
# TOOL_FUNCTION_EXTERNAL_DEP=moment,lodash,pg,mysql2,mongodb,ioredis,redis,typeorm,puppeteer,playwright,@zilliz/milvus2-sdk-node
# ALLOW_BUILTIN_DEP=false


Expand Down
4 changes: 4 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,8 @@ ENV PUPPETEER_EXECUTABLE_PATH=/usr/bin/chromium-browser
COPY --from=build /usr/local/lib/node_modules /usr/local/lib/node_modules
COPY --from=build /usr/local/bin /usr/local/bin

RUN mkdir -p /home/node/.flowise && chown -R node:node /home/node/.flowise

USER node

ENTRYPOINT ["flowise", "start"]
8 changes: 4 additions & 4 deletions docker/docker-compose-queue-prebuilt.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,11 @@ services:
ports:
- '${PORT:-3000}:${PORT:-3000}'
volumes:
- ~/.flowise:/root/.flowise
- ~/.flowise:/home/node/.flowise
environment:
# --- Essential Flowise Vars ---
- PORT=${PORT:-3000}
- DATABASE_PATH=${DATABASE_PATH:-/root/.flowise}
- DATABASE_PATH=${DATABASE_PATH:-/home/node/.flowise}
- DATABASE_TYPE=${DATABASE_TYPE}
- DATABASE_PORT=${DATABASE_PORT}
- DATABASE_HOST=${DATABASE_HOST}
Expand Down Expand Up @@ -180,11 +180,11 @@ services:
container_name: flowise-worker
restart: always
volumes:
- ~/.flowise:/root/.flowise
- ~/.flowise:/home/node/.flowise
environment:
# --- Essential Flowise Vars ---
- WORKER_PORT=${WORKER_PORT:-5566}
- DATABASE_PATH=${DATABASE_PATH:-/root/.flowise}
- DATABASE_PATH=${DATABASE_PATH:-/home/node/.flowise}
- DATABASE_TYPE=${DATABASE_TYPE}
- DATABASE_PORT=${DATABASE_PORT}
- DATABASE_HOST=${DATABASE_HOST}
Expand Down
20 changes: 10 additions & 10 deletions docker/docker-compose-queue-source.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,14 +20,14 @@ services:
- '${PORT}:${PORT}'
volumes:
# Mount local .flowise to container's default location
- ../.flowise:/root/.flowise
- ../.flowise:/home/node/.flowise
environment:
# --- Essential Flowise Vars ---
- PORT=${PORT:-3000}
- DATABASE_PATH=/root/.flowise
- SECRETKEY_PATH=/root/.flowise
- LOG_PATH=/root/.flowise/logs
- BLOB_STORAGE_PATH=/root/.flowise/storage
- DATABASE_PATH=/home/node/.flowise
- SECRETKEY_PATH=/home/node/.flowise
- LOG_PATH=/home/node/.flowise/logs
- BLOB_STORAGE_PATH=/home/node/.flowise/storage
# --- Queue Vars (Main Instance) ---
- MODE=queue
- QUEUE_NAME=flowise-queue # Ensure this matches worker
Expand All @@ -44,14 +44,14 @@ services:
dockerfile: docker/worker/Dockerfile # Ensure this path is correct
volumes:
# Mount same local .flowise to worker
- ../.flowise:/root/.flowise
- ../.flowise:/home/node/.flowise
environment:
# --- Essential Flowise Vars ---
- WORKER_PORT=${WORKER_PORT:-5566} # Port for worker healthcheck
- DATABASE_PATH=/root/.flowise
- SECRETKEY_PATH=/root/.flowise
- LOG_PATH=/root/.flowise/logs
- BLOB_STORAGE_PATH=/root/.flowise/storage
- DATABASE_PATH=/home/node/.flowise
- SECRETKEY_PATH=/home/node/.flowise
- LOG_PATH=/home/node/.flowise/logs
- BLOB_STORAGE_PATH=/home/node/.flowise/storage
# --- Queue Vars (Main Instance) ---
- MODE=queue
- QUEUE_NAME=flowise-queue # Ensure this matches worker
Expand Down
2 changes: 1 addition & 1 deletion docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -157,5 +157,5 @@ services:
retries: 5
start_period: 30s
volumes:
- ~/.flowise:/root/.flowise
- ~/.flowise:/home/node/.flowise
entrypoint: /bin/sh -c "sleep 3; flowise start"
2 changes: 1 addition & 1 deletion docker/worker/.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ WORKER_PORT=5566
# LOG_SANITIZE_BODY_FIELDS=password,pwd,pass,secret,token,apikey,api_key,accesstoken,access_token,refreshtoken,refresh_token,clientsecret,client_secret,privatekey,private_key,secretkey,secret_key,auth,authorization,credential,credentials
# LOG_SANITIZE_HEADER_FIELDS=authorization,x-api-key,x-auth-token,cookie
# TOOL_FUNCTION_BUILTIN_DEP=crypto,fs
# TOOL_FUNCTION_EXTERNAL_DEP=moment,lodash
# TOOL_FUNCTION_EXTERNAL_DEP=moment,lodash,pg,mysql2,mongodb,ioredis,redis,typeorm,puppeteer,playwright,@zilliz/milvus2-sdk-node
# ALLOW_BUILTIN_DEP=false


Expand Down
4 changes: 4 additions & 0 deletions docker/worker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@ COPY docker/worker/healthcheck/healthcheck.js .
# Set the main working directory back
WORKDIR /usr/src

RUN chown -R node:node /usr/src /app/healthcheck

USER node

# Environment variables for port configuration
ENV WORKER_PORT=5566

Expand Down
2 changes: 1 addition & 1 deletion docker/worker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -156,5 +156,5 @@ services:
retries: 5
start_period: 30s
volumes:
- ~/.flowise:/root/.flowise
- ~/.flowise:/home/node/.flowise
entrypoint: /bin/sh -c "node /app/healthcheck/healthcheck.js & sleep 5 && pnpm run start-worker"
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
import { AxiosRequestConfig } from 'axios'
import { secureAxiosRequest } from '../../../src/httpSecurity'
import { getCredentialData, getCredentialParam, processTemplateVariables, parseJsonBody } from '../../../src/utils'
import { isValidURL } from '../../../src/validator'
import { DataSource } from 'typeorm'
import { BaseMessageLike } from '@langchain/core/messages'
import { updateFlowState } from '../utils'
Expand Down Expand Up @@ -183,6 +184,8 @@ class ExecuteFlow_Agentflow implements INode {
const credentialData = await getCredentialData(nodeData.credential ?? '', options)
const chatflowApiKey = getCredentialParam('chatflowApiKey', credentialData, nodeData)

if (baseURL && !isValidURL(baseURL)) throw new Error('Invalid base URL: must be a valid URL')
Comment thread
0xi4o marked this conversation as resolved.
Outdated

if (selectedFlowId === options.chatflowid) throw new Error('Cannot call the same agentflow!')

let headers: Record<string, string> = {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -239,19 +239,20 @@ class ExecuteFlow_SeqAgents implements INode {
// Create additional sandbox variables
const additionalSandbox: ICommonObject = {
$callOptions: callOptions,
$callBody: body
$callBody: body,
$apiURL: `${baseURL}/api/v1/prediction/${selectedFlowId}`
}

const sandbox = createCodeExecutionSandbox(flowInput, variables, flow, additionalSandbox)

const code = `
const fetch = require('node-fetch');
const url = "${baseURL}/api/v1/prediction/${selectedFlowId}";
const url = $apiURL;

const body = $callBody;

const options = $callOptions;

try {
const response = await fetch(url, options);
const resp = await response.json();
Expand Down
5 changes: 3 additions & 2 deletions packages/components/nodes/tools/AgentAsTool/AgentAsTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -345,7 +345,7 @@ class AgentflowTool extends StructuredTool {

const code = `
const fetch = require('node-fetch');
const url = "${this.baseURL}/api/v1/prediction/${this.agentflowid}";
const url = $apiURL;

const body = $callBody;

Expand All @@ -364,7 +364,8 @@ try {
// Create additional sandbox variables
const additionalSandbox: ICommonObject = {
$callOptions: options,
$callBody: body
$callBody: body,
$apiURL: `${this.baseURL}/api/v1/prediction/${this.agentflowid}`
}

const sandbox = createCodeExecutionSandbox('', [], {}, additionalSandbox)
Expand Down
5 changes: 3 additions & 2 deletions packages/components/nodes/tools/ChatflowTool/ChatflowTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,7 +353,7 @@ class ChatflowTool extends StructuredTool {

const code = `
const fetch = require('node-fetch');
const url = "${this.baseURL}/api/v1/prediction/${this.chatflowid}";
const url = $apiURL;

const body = $callBody;

Expand All @@ -372,7 +372,8 @@ try {
// Create additional sandbox variables
const additionalSandbox: ICommonObject = {
$callOptions: options,
$callBody: body
$callBody: body,
$apiURL: `${this.baseURL}/api/v1/prediction/${this.chatflowid}`
}

const sandbox = createCodeExecutionSandbox('', [], {}, additionalSandbox)
Expand Down
60 changes: 59 additions & 1 deletion packages/components/src/utils.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
import { removeInvalidImageMarkdown, convertRequireToImport, COMMONJS_REQUIRE_REGEX, IMPORT_EXTRACTION_REGEX } from './utils'
import {
removeInvalidImageMarkdown,
convertRequireToImport,
COMMONJS_REQUIRE_REGEX,
IMPORT_EXTRACTION_REGEX,
executeJavaScriptCode
} from './utils'

describe('removeInvalidImageMarkdown', () => {
describe('strips non-http/https image markdown', () => {
Expand Down Expand Up @@ -229,3 +235,55 @@ describe('Import extraction regex (utils.ts line 1596 pattern)', () => {
expect(extractModules('console.log("hello")')).toEqual([])
})
})

// ---------------------------------------------------------------------------
// NodeVM sandbox — availableDependencies allowlist
// ---------------------------------------------------------------------------

describe('NodeVM sandbox — availableDependencies allowlist', () => {
afterEach(() => {
delete process.env.ALLOW_BUILTIN_DEP
delete process.env.TOOL_FUNCTION_EXTERNAL_DEP
})

describe('high-risk packages are blocked even when ALLOW_BUILTIN_DEP=true', () => {
beforeEach(() => {
process.env.ALLOW_BUILTIN_DEP = 'true'
})

const removedPackages = [
'pg',
'mysql2',
'mongodb',
'ioredis',
'redis',
'typeorm',
'puppeteer',
'playwright',
'@zilliz/milvus2-sdk-node'
]

test.each(removedPackages)(
"require('%s') is denied",
async (pkg) => {
await expect(
executeJavaScriptCode(`const m = require('${pkg}'); return 'loaded'`, {}, { timeout: 10000 })
).rejects.toThrow()
},
15000
)
})

it('packages remaining in availableDependencies are still accessible with ALLOW_BUILTIN_DEP=true', async () => {
process.env.ALLOW_BUILTIN_DEP = 'true'
const result = await executeJavaScriptCode(`const cheerio = require('cheerio'); return typeof cheerio.load`, {}, { timeout: 10000 })
expect(result).toBe('function')
}, 15000)

it('a removed package becomes accessible via TOOL_FUNCTION_EXTERNAL_DEP', async () => {
process.env.ALLOW_BUILTIN_DEP = 'true'
process.env.TOOL_FUNCTION_EXTERNAL_DEP = 'pg'
const result = await executeJavaScriptCode(`const { Client } = require('pg'); return typeof Client`, {}, { timeout: 10000 })
expect(result).toBe('function')
}, 15000)
})
15 changes: 4 additions & 11 deletions packages/components/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,6 @@ export const availableDependencies = [
'@qdrant/js-client-rest',
'@supabase/supabase-js',
'@upstash/redis',
'@zilliz/milvus2-sdk-node',
'apify-client',
'cheerio',
'chromadb',
Expand All @@ -97,32 +96,24 @@ export const availableDependencies = [
'google-auth-library',
'graphql',
'html-to-text',
'ioredis',
'langchain',
'langfuse',
'langsmith',
'langwatch',
'linkifyjs',
'lunary',
'mammoth',
'mongodb',
'mysql2',
'node-html-markdown',
'notion-to-md',
'openai',
'pdf-parse',
'pdfjs-dist',
'pg',
'playwright',
'puppeteer',
'redis',
'replicate',
'srt-parser-2',
'typeorm',
'weaviate-client'
]

const defaultAllowExternalDependencies = ['axios', 'moment', 'node-fetch']
const defaultAllowExternalDependencies = ['axios', 'node-fetch']

export const defaultAllowBuiltInDep = ['assert', 'buffer', 'crypto', 'events', 'path', 'querystring', 'timers', 'url', 'zlib']

Expand Down Expand Up @@ -1780,6 +1771,7 @@ export const executeJavaScriptCode = async (
},
eval: false,
wasm: false,
fixAsync: true,
timeout: timeoutMs
}

Expand All @@ -1789,7 +1781,8 @@ export const executeJavaScriptCode = async (
...nodeVMOptions,
require: defaultNodeVMOptions.require,
eval: false,
wasm: false
wasm: false,
fixAsync: true
}

const vm = new NodeVM(finalNodeVMOptions)
Expand Down
59 changes: 58 additions & 1 deletion packages/components/src/validator.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { isPathTraversal, isUnsafeFilePath, validateMimeTypeAndExtensionMatch, validateVectorStorePath } from './validator'
import { isPathTraversal, isUnsafeFilePath, isValidURL, validateMimeTypeAndExtensionMatch, validateVectorStorePath } from './validator'
import path from 'path'
import { getUserHome } from './utils'

Expand Down Expand Up @@ -466,3 +466,60 @@ describe('validateVectorStorePath', () => {
})
})
})

describe('isValidURL', () => {
describe('accepts valid http/https URLs', () => {
it.each([
['bare http host', 'http://localhost:3000'],
['https with path', 'https://flowise.example.com/api'],
['http with port and path', 'http://192.168.1.1:3000/api/v1'],
['https with query string', 'https://example.com/search?q=hello']
])('should accept %s', (_desc, url) => {
expect(isValidURL(url)).toBe(true)
})
})

describe('rejects non-http(s) protocols', () => {
it.each([
['file protocol', 'file:///etc/passwd'],
['javascript protocol', 'javascript:alert(1)'],
['ftp protocol', 'ftp://example.com'],
['data URI', 'data:text/html,<script>alert(1)</script>']
])('should reject %s', (_desc, url) => {
expect(isValidURL(url)).toBe(false)
})
})

describe('rejects URLs with hash fragments (CVE-2022-24785 bypass entry point)', () => {
it.each([
['plain hash', 'http://localhost:3000/#section'],
['hash with injection payload', 'https://evil.com/#";\nrequire("child_process").exec("id");//'],
['hash with quote escape', 'http://localhost:3000/#";malicious;//']
])('should reject %s', (_desc, url) => {
expect(isValidURL(url)).toBe(false)
})
})

describe('rejects URLs containing JS string-breaking characters', () => {
it.each([
['double quote', 'http://localhost:3000/path"suffix'],
['single quote', "http://localhost:3000/path'suffix"],
['backslash', 'http://localhost:3000/path\\suffix'],
['newline', 'http://localhost:3000/path\nsuffix'],
['carriage return', 'http://localhost:3000/path\rsuffix'],
['tab', 'http://localhost:3000/path\tsuffix']
])('should reject URL with %s', (_desc, url) => {
expect(isValidURL(url)).toBe(false)
})
})

describe('rejects malformed or empty inputs', () => {
it.each([
['empty string', ''],
['not a URL', 'not-a-url'],
['relative path', '/api/v1/prediction/abc']
])('should reject %s', (_desc, url) => {
expect(isValidURL(url)).toBe(false)
})
})
})
Loading
Loading