Skip to content

Commit 45fc4c3

Browse files
Add protections for loop-bound injections
1 parent b63d771 commit 45fc4c3

File tree

3 files changed

+105
-5
lines changed

3 files changed

+105
-5
lines changed

packages/components/evaluation/EvaluationRunner.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,27 @@ export class EvaluationRunner {
8888

8989
public async runEvaluations(data: ICommonObject) {
9090
const chatflowIds = JSON.parse(data.chatflowId)
91+
92+
// Validate chatflowIds is an actual array to prevent DoS attacks
93+
if (!Array.isArray(chatflowIds)) {
94+
throw new Error('chatflowId must be a valid array')
95+
}
96+
97+
const MAX_CHATFLOWS = 1000
98+
if (chatflowIds.length > MAX_CHATFLOWS) {
99+
throw new Error(`Cannot evaluate more than ${MAX_CHATFLOWS} chatflows at once`)
100+
}
101+
102+
// Validate dataset.rows is an actual array to prevent DoS attacks
103+
if (!data.dataset || !Array.isArray(data.dataset.rows)) {
104+
throw new Error('dataset.rows must be a valid array')
105+
}
106+
107+
const MAX_ROWS = 1000
108+
if (data.dataset.rows.length > MAX_ROWS) {
109+
throw new Error(`Dataset cannot exceed ${MAX_ROWS} rows`)
110+
}
111+
91112
const returnData: ICommonObject = {}
92113
returnData.evaluationId = data.evaluationId
93114
returnData.runDate = new Date()

packages/server/src/services/evaluations/EvaluatorRunner.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,31 @@ interface EvaluatorReturnType {
1010
result: 'Pass' | 'Fail' | 'Error'
1111
}
1212

13+
// Limit maximum array sizes to prevent DoS attacks
14+
const MAX_OUTPUTS = 10000
15+
const MAX_EVALUATORS = 1000
16+
const MAX_SPLIT_VALUES = 1000
17+
1318
export const runAdditionalEvaluators = async (
1419
metricsArray: ICommonObject[],
1520
actualOutputArray: string[],
1621
errorArray: string[],
1722
selectedEvaluators: string[],
1823
workspaceId: string
1924
) => {
25+
// Validate inputs are arrays and enforce size limits
26+
if (!Array.isArray(actualOutputArray) || !Array.isArray(selectedEvaluators)) {
27+
throw new Error('Invalid input: expected arrays')
28+
}
29+
30+
if (actualOutputArray.length > MAX_OUTPUTS) {
31+
throw new Error(`Too many outputs: maximum allowed is ${MAX_OUTPUTS}`)
32+
}
33+
34+
if (selectedEvaluators.length > MAX_EVALUATORS) {
35+
throw new Error(`Too many evaluators: maximum allowed is ${MAX_EVALUATORS}`)
36+
}
37+
2038
const evaluationResults: any[] = []
2139
const evaluatorDict: any = {}
2240

@@ -103,6 +121,11 @@ export const runAdditionalEvaluators = async (
103121
case 'ContainsAny':
104122
passed = false
105123
splitValues = value.split(',').map((v) => v.trim().toLowerCase()) // Split, trim, and convert to lowercase
124+
// Limit split values to prevent unbounded iteration
125+
if (splitValues.length > MAX_SPLIT_VALUES) {
126+
throw new Error(`Too many split values: maximum allowed is ${MAX_SPLIT_VALUES}`)
127+
}
128+
106129
for (let i = 0; i < splitValues.length; i++) {
107130
if (actualOutput.includes(splitValues[i])) {
108131
passed = true
@@ -117,6 +140,11 @@ export const runAdditionalEvaluators = async (
117140
case 'ContainsAll':
118141
passed = true
119142
splitValues = value.split(',').map((v) => v.trim().toLowerCase()) // Split, trim, and convert to lowercase
143+
// Limit split values to prevent unbounded iteration
144+
if (splitValues.length > MAX_SPLIT_VALUES) {
145+
throw new Error(`Too many split values: maximum allowed is ${MAX_SPLIT_VALUES}`)
146+
}
147+
120148
for (let i = 0; i < splitValues.length; i++) {
121149
if (!actualOutput.includes(splitValues[i])) {
122150
passed = false
@@ -131,6 +159,11 @@ export const runAdditionalEvaluators = async (
131159
case 'DoesNotContainAny':
132160
passed = true
133161
splitValues = value.split(',').map((v) => v.trim().toLowerCase()) // Split, trim, and convert to lowercase
162+
// Limit split values to prevent unbounded iteration
163+
if (splitValues.length > MAX_SPLIT_VALUES) {
164+
throw new Error(`Too many split values: maximum allowed is ${MAX_SPLIT_VALUES}`)
165+
}
166+
134167
for (let i = 0; i < splitValues.length; i++) {
135168
if (actualOutput.includes(splitValues[i])) {
136169
passed = false
@@ -145,6 +178,11 @@ export const runAdditionalEvaluators = async (
145178
case 'DoesNotContainAll':
146179
passed = true
147180
splitValues = value.split(',').map((v) => v.trim().toLowerCase()) // Split, trim, and convert to lowercase
181+
// Limit split values to prevent unbounded iteration
182+
if (splitValues.length > MAX_SPLIT_VALUES) {
183+
throw new Error(`Too many split values: maximum allowed is ${MAX_SPLIT_VALUES}`)
184+
}
185+
148186
for (let i = 0; i < splitValues.length; i++) {
149187
if (actualOutput.includes(splitValues[i])) {
150188
passed = false

packages/server/src/services/evaluations/index.ts

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -72,14 +72,44 @@ const createEvaluation = async (body: ICommonObject, baseURL: string, orgId: str
7272
const row = appServer.AppDataSource.getRepository(Evaluation).create(newEval)
7373
row.average_metrics = JSON.stringify({})
7474

75+
// Parse and validate evaluator arrays to prevent DoS attacks
76+
const chatflowTypes = body.chatflowType ? JSON.parse(body.chatflowType) : []
77+
if (!Array.isArray(chatflowTypes)) {
78+
throw new Error('chatflowType must be a valid array')
79+
}
80+
81+
const MAX_CHATFLOW_TYPES = 1000
82+
if (chatflowTypes.length > MAX_CHATFLOW_TYPES) {
83+
throw new Error(`Cannot evaluate more than ${MAX_CHATFLOW_TYPES} chatflow types at once`)
84+
}
85+
86+
const simpleEvaluators = body.selectedSimpleEvaluators.length > 0 ? JSON.parse(body.selectedSimpleEvaluators) : []
87+
if (!Array.isArray(simpleEvaluators)) {
88+
throw new Error('selectedSimpleEvaluators must be a valid array')
89+
}
90+
91+
const MAX_EVALUATORS = 1000
92+
if (simpleEvaluators.length > MAX_EVALUATORS) {
93+
throw new Error(`Cannot use more than ${MAX_EVALUATORS} simple evaluators at once`)
94+
}
95+
7596
const additionalConfig: ICommonObject = {
76-
chatflowTypes: body.chatflowType ? JSON.parse(body.chatflowType) : [],
97+
chatflowTypes: chatflowTypes,
7798
datasetAsOneConversation: body.datasetAsOneConversation,
78-
simpleEvaluators: body.selectedSimpleEvaluators.length > 0 ? JSON.parse(body.selectedSimpleEvaluators) : []
99+
simpleEvaluators: simpleEvaluators
79100
}
80101

81102
if (body.evaluationType === 'llm') {
82-
additionalConfig.lLMEvaluators = body.selectedLLMEvaluators.length > 0 ? JSON.parse(body.selectedLLMEvaluators) : []
103+
const lLMEvaluators = body.selectedLLMEvaluators.length > 0 ? JSON.parse(body.selectedLLMEvaluators) : []
104+
if (!Array.isArray(lLMEvaluators)) {
105+
throw new Error('selectedLLMEvaluators must be a valid array')
106+
}
107+
108+
if (lLMEvaluators.length > MAX_EVALUATORS) {
109+
throw new Error(`Cannot use more than ${MAX_EVALUATORS} LLM evaluators at once`)
110+
}
111+
112+
additionalConfig.lLMEvaluators = lLMEvaluators
83113
additionalConfig.llmConfig = {
84114
credentialId: body.credentialId,
85115
llm: body.llm,
@@ -123,6 +153,17 @@ const createEvaluation = async (body: ICommonObject, baseURL: string, orgId: str
123153
// When chatflow has an APIKey
124154
const apiKeys: { chatflowId: string; apiKey: string }[] = []
125155
const chatflowIds = JSON.parse(body.chatflowId)
156+
157+
// Validate chatflowIds is an actual array to prevent DoS attacks
158+
if (!Array.isArray(chatflowIds)) {
159+
throw new Error('chatflowId must be a valid array')
160+
}
161+
162+
const MAX_CHATFLOWS_EVAL = 100
163+
if (chatflowIds.length > MAX_CHATFLOWS_EVAL) {
164+
throw new Error(`Cannot evaluate more than ${MAX_CHATFLOWS_EVAL} chatflows at once`)
165+
}
166+
126167
for (let i = 0; i < chatflowIds.length; i++) {
127168
const chatflowId = chatflowIds[i]
128169
const cFlow = await appServer.AppDataSource.getRepository(ChatFlow).findOneBy({
@@ -246,7 +287,7 @@ const createEvaluation = async (body: ICommonObject, baseURL: string, orgId: str
246287
metricsArray,
247288
actualOutputArray,
248289
errorArray,
249-
body.selectedSimpleEvaluators.length > 0 ? JSON.parse(body.selectedSimpleEvaluators) : [],
290+
additionalConfig.simpleEvaluators,
250291
workspaceId
251292
)
252293

@@ -257,7 +298,7 @@ const createEvaluation = async (body: ICommonObject, baseURL: string, orgId: str
257298

258299
if (body.evaluationType === 'llm') {
259300
resultRow.llmConfig = additionalConfig.llmConfig
260-
resultRow.LLMEvaluators = body.selectedLLMEvaluators.length > 0 ? JSON.parse(body.selectedLLMEvaluators) : []
301+
resultRow.LLMEvaluators = additionalConfig.lLMEvaluators
261302
const llmEvaluatorMap: { evaluatorId: string; evaluator: any }[] = []
262303
for (let i = 0; i < resultRow.LLMEvaluators.length; i++) {
263304
const evaluatorId = resultRow.LLMEvaluators[i]

0 commit comments

Comments
 (0)