mirror of
https://github.com/labring/FastGPT.git
synced 2025-10-13 14:29:40 +00:00
perf: variabel replace;Feat: prompt optimizer code (#5453)
* feat: add prompt optimizer (#5444) * feat: add prompt optimizer * fix * perf: variabel replace * perf: prompt optimizer code * feat: init charts shell * perf: user error remove --------- Co-authored-by: heheer <heheer@sealos.io>
This commit is contained in:
632
test/cases/service/core/app/workflow/dispatch/http.test.ts
Normal file
632
test/cases/service/core/app/workflow/dispatch/http.test.ts
Normal file
@@ -0,0 +1,632 @@
|
||||
import { describe, it, expect } from 'vitest';
|
||||
import { replaceJsonBodyString } from '@fastgpt/service/core/workflow/dispatch/tools/http468';
|
||||
import type { RuntimeNodeItemType } from '@fastgpt/global/core/workflow/runtime/type';
|
||||
|
||||
describe('replaceJsonBodyString', () => {
|
||||
// Mock runtime nodes for testing
|
||||
const mockRuntimeNodes = [
|
||||
{
|
||||
nodeId: 'node1',
|
||||
outputs: [
|
||||
{ id: 'output1', value: 'Hello World', valueType: 'string' },
|
||||
{ id: 'output2', value: 42, valueType: 'number' },
|
||||
{ id: 'output3', value: true, valueType: 'boolean' },
|
||||
{ id: 'output4', value: { nested: 'value' }, valueType: 'object' },
|
||||
{ id: 'output5', value: [1, 2, 3], valueType: 'array' }
|
||||
],
|
||||
inputs: []
|
||||
}
|
||||
] as unknown as RuntimeNodeItemType[];
|
||||
|
||||
const mockVariables = {
|
||||
userName: 'John Doe',
|
||||
userAge: 30,
|
||||
isActive: true,
|
||||
userProfile: { name: 'John', email: 'john@example.com' },
|
||||
tags: ['developer', 'tester']
|
||||
};
|
||||
|
||||
const mockAllVariables = {
|
||||
...mockVariables,
|
||||
systemVar: 'system_value',
|
||||
nullVar: null,
|
||||
undefinedVar: undefined,
|
||||
emptyString: '',
|
||||
zeroNumber: 0,
|
||||
falseBool: false
|
||||
};
|
||||
|
||||
const mockProps = {
|
||||
variables: mockVariables,
|
||||
allVariables: mockAllVariables,
|
||||
runtimeNodes: mockRuntimeNodes
|
||||
};
|
||||
|
||||
describe('Basic variable replacement functionality', () => {
|
||||
it('should correctly replace string variables', () => {
|
||||
const input = '{"name": "{{userName}}", "greeting": "Hello {{userName}}"}';
|
||||
const expected = '{"name": "John Doe", "greeting": "Hello John Doe"}';
|
||||
|
||||
const result = replaceJsonBodyString({ text: input }, mockProps);
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('should correctly replace number variables', () => {
|
||||
const input =
|
||||
'{"age": {{userAge}}, "doubled": {{userAge}}, "calculation": "{{userAge}} years old"}';
|
||||
const expected = '{"age": 30, "doubled": 30, "calculation": "30 years old"}';
|
||||
|
||||
const result = replaceJsonBodyString({ text: input }, mockProps);
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('should correctly replace boolean variables', () => {
|
||||
const input =
|
||||
'{"active": {{isActive}}, "inactive": {{falseBool}}, "status": "User is {{isActive}}"}';
|
||||
const expected = '{"active": true, "inactive": false, "status": "User is true"}';
|
||||
|
||||
const result = replaceJsonBodyString({ text: input }, mockProps);
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('should correctly replace object variables', () => {
|
||||
const input = '{"profile": {{userProfile}}, "info": "Profile: {{userProfile}}"}';
|
||||
const expected =
|
||||
'{"profile": {"name":"John","email":"john@example.com"}, "info": "Profile: {"name":"John","email":"john@example.com"}"}';
|
||||
|
||||
const result = replaceJsonBodyString({ text: input }, mockProps);
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('should correctly replace array variables and node output variables', () => {
|
||||
const input =
|
||||
'{"tags": {{tags}}, "nodeOutput": "{{$node1.output1$}}", "numbers": {{$node1.output5$}}}';
|
||||
const expected =
|
||||
'{"tags": ["developer","tester"], "nodeOutput": "Hello World", "numbers": [1,2,3]}';
|
||||
|
||||
const result = replaceJsonBodyString({ text: input }, mockProps);
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Security and boundary testing', () => {
|
||||
it('should prevent circular references', () => {
|
||||
const maliciousProps = {
|
||||
...mockProps,
|
||||
allVariables: {
|
||||
...mockAllVariables,
|
||||
selfRef: '{{selfRef}}', // Self reference
|
||||
circularA: '{{circularB}}',
|
||||
circularB: '{{circularA}}'
|
||||
}
|
||||
};
|
||||
|
||||
const input = '{"self": "{{selfRef}}", "circular": "{{circularA}}"}';
|
||||
// Should keep circular references unreplaced
|
||||
const result = replaceJsonBodyString({ text: input }, maliciousProps);
|
||||
expect(result).toContain('{{selfRef}}');
|
||||
expect(result).toContain('{{circularB}}');
|
||||
});
|
||||
|
||||
it('should prevent excessive nesting depth', () => {
|
||||
const deepNestingProps = {
|
||||
...mockProps,
|
||||
allVariables: {
|
||||
...mockAllVariables,
|
||||
level1: '{{level2}}',
|
||||
level2: '{{level3}}',
|
||||
level3: '{{level4}}',
|
||||
level4: '{{level5}}',
|
||||
level5: '{{level6}}',
|
||||
level6: '{{level7}}',
|
||||
level7: '{{level8}}',
|
||||
level8: '{{level9}}',
|
||||
level9: '{{level10}}',
|
||||
level10: '{{level11}}',
|
||||
level11: '{{level12}}',
|
||||
level12: 'final_value'
|
||||
}
|
||||
};
|
||||
|
||||
const input = '{"deep": "{{level1}}"}';
|
||||
// Should handle but limit depth
|
||||
const result = replaceJsonBodyString({ text: input }, deepNestingProps);
|
||||
expect(result).toBeDefined();
|
||||
|
||||
expect(result).toBe('{"deep": "{{level12}}"}');
|
||||
// Should eventually resolve to a valid value
|
||||
});
|
||||
|
||||
it('should handle large number of variables performance test', () => {
|
||||
const manyVariables: Record<string, any> = {};
|
||||
let input = '{';
|
||||
|
||||
// Create 1000 variables
|
||||
for (let i = 0; i < 1000; i++) {
|
||||
manyVariables[`var${i}`] = `value${i}`;
|
||||
input += `"var${i}": "{{var${i}}}",`;
|
||||
}
|
||||
input = input.slice(0, -1) + '}'; // Remove last comma
|
||||
|
||||
const largeProps = {
|
||||
...mockProps,
|
||||
allVariables: { ...mockAllVariables, ...manyVariables }
|
||||
};
|
||||
|
||||
const result = replaceJsonBodyString({ text: input }, largeProps);
|
||||
expect(result).toBeDefined();
|
||||
expect(result).toContain('value0');
|
||||
expect(result).toContain('value999');
|
||||
// Verify performance and correctness
|
||||
});
|
||||
|
||||
it('should prevent regex attacks', () => {
|
||||
const maliciousProps = {
|
||||
...mockProps,
|
||||
allVariables: {
|
||||
...mockAllVariables,
|
||||
// Contains special regex characters
|
||||
maliciousVar: '.*+?^${}()|[]\\',
|
||||
regexAttack: '(.*){999999}', // Potential regex attack
|
||||
specialChars: '\\n\\r\\t"\'`<>&'
|
||||
}
|
||||
};
|
||||
|
||||
const input =
|
||||
'{"malicious": "{{maliciousVar}}", "attack": "{{regexAttack}}", "special": "{{specialChars}}"}';
|
||||
const result = replaceJsonBodyString({ text: input }, maliciousProps);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(typeof result).toBe('string');
|
||||
// Should properly escape special characters
|
||||
expect(result).toContain('.*+?^${}()|[]\\\\');
|
||||
});
|
||||
|
||||
it('should handle quote injection and escape characters', () => {
|
||||
const maliciousProps = {
|
||||
...mockProps,
|
||||
allVariables: {
|
||||
...mockAllVariables,
|
||||
quotedVar: '"malicious"value"',
|
||||
escapedVar: '\\"escaped\\"',
|
||||
jsonInjection: '", "injected": "hacked',
|
||||
scriptTag: '<script>alert("xss")</script>',
|
||||
newlineAttack: 'line1\\nline2\\r\\nline3'
|
||||
}
|
||||
};
|
||||
|
||||
const input =
|
||||
'{"quoted": "{{quotedVar}}", "escaped": "{{escapedVar}}", "injection": "{{jsonInjection}}", "script": "{{scriptTag}}", "newlines": "{{newlineAttack}}"}';
|
||||
const result = replaceJsonBodyString({ text: input }, maliciousProps);
|
||||
|
||||
expect(result).toBeDefined();
|
||||
expect(typeof result).toBe('string');
|
||||
|
||||
// Should properly escape quotes
|
||||
expect(result).toContain('\\"malicious\\"value\\"');
|
||||
|
||||
// Should handle newlines
|
||||
expect(result).toContain('line1\\\\nline2\\\\r\\\\nline3');
|
||||
|
||||
// Should generate valid JSON
|
||||
try {
|
||||
JSON.parse(result);
|
||||
} catch (e) {
|
||||
// If parsing fails, there might be injection attacks or escaping issues
|
||||
// Further validation needed here
|
||||
expect(e).toBeDefined();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('Edge cases', () => {
|
||||
it('should handle empty string', () => {
|
||||
const result = replaceJsonBodyString({ text: '' }, mockProps);
|
||||
expect(result).toBe('');
|
||||
});
|
||||
|
||||
it('should handle static content without variables', () => {
|
||||
const input = '{"static": "value", "number": 123}';
|
||||
const result = replaceJsonBodyString({ text: input }, mockProps);
|
||||
expect(result).toBe(input);
|
||||
});
|
||||
|
||||
it('should handle null and undefined values', () => {
|
||||
const input = '{"nullVar": {{nullVar}}, "undefinedVar": {{undefinedVar}}}';
|
||||
const expected = '{"nullVar": null, "undefinedVar": null}';
|
||||
|
||||
const result = replaceJsonBodyString({ text: input }, mockProps);
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
|
||||
it('should handle non-existent variables', () => {
|
||||
const input = '{"missing": "{{nonExistentVar}}", "static": "value"}';
|
||||
const expected = '{"missing": "null", "static": "value"}';
|
||||
|
||||
const result = replaceJsonBodyString({ text: input }, mockProps);
|
||||
expect(result).toBe(expected);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Complex robustness and compatibility tests', () => {
|
||||
// Test Case 1: Nested JSON with mixed variable types and complex escape scenarios
|
||||
it('Complex Test 1: Deep nested JSON with mixed data types, escape characters and Unicode', () => {
|
||||
const complexProps = {
|
||||
...mockProps,
|
||||
allVariables: {
|
||||
...mockAllVariables,
|
||||
unicodeText: '你好世界 🌍 Hello\nWorld',
|
||||
complexObject: {
|
||||
nested: {
|
||||
array: [{ key: 'value"with"quotes' }, null, undefined],
|
||||
special: 'tab\there\nnewline\rcarriage\\"quotes\\\'single'
|
||||
}
|
||||
},
|
||||
htmlContent: '<div class="test" data-value=\'mixed"quotes\'>Content & more</div>',
|
||||
jsonLikeString: '{"fake": "json", "number": 123, "bool": true}',
|
||||
emptyValues: { empty: '', zero: 0, false: false, null: null }
|
||||
}
|
||||
};
|
||||
|
||||
const input = `{
|
||||
"metadata": {
|
||||
"title": "{{unicodeText}}",
|
||||
"description": "Testing \\"complex\\" scenarios",
|
||||
"nested": {{complexObject}},
|
||||
"htmlSnippet": "{{htmlContent}}"
|
||||
},
|
||||
"payload": {
|
||||
"jsonString": "{{jsonLikeString}}",
|
||||
"emptyData": {{emptyValues}},
|
||||
"nodeOutput": "{{$node1.output4$}}",
|
||||
"arrayData": {{$node1.output5$}}
|
||||
},
|
||||
"validation": {
|
||||
"hasNewlines": "Text with\\nembedded\\tcharacters: {{unicodeText}}",
|
||||
"quoteMixing": "\\"Outer quotes\\" and '{{htmlContent}}' content"
|
||||
}
|
||||
}`;
|
||||
|
||||
const result = replaceJsonBodyString({ text: input }, complexProps);
|
||||
|
||||
// Verify it's valid JSON after replacement
|
||||
expect(() => JSON.parse(result)).not.toThrow();
|
||||
|
||||
// Verify complex nested structures are preserved
|
||||
const parsed = JSON.parse(result);
|
||||
expect(parsed.metadata.nested.nested.array).toHaveLength(3);
|
||||
expect(parsed.payload.emptyData.zero).toBe(0);
|
||||
expect(parsed.payload.emptyData.false).toBe(false);
|
||||
expect(parsed.payload.emptyData.null).toBe(null);
|
||||
|
||||
// Verify Unicode and special characters are handled
|
||||
expect(parsed.metadata.title).toContain('你好世界');
|
||||
expect(parsed.metadata.title).toContain('🌍');
|
||||
});
|
||||
|
||||
// Test Case 2: Variable substitution in complex nested structures with edge cases
|
||||
it('Complex Test 2: Complex variable interpolation and data type handling', () => {
|
||||
const edgeCaseProps = {
|
||||
...mockProps,
|
||||
allVariables: {
|
||||
...mockAllVariables,
|
||||
leadingTrailingSpaces: ' spaced content ',
|
||||
numberAsString: '12345',
|
||||
boolAsString: 'true',
|
||||
arrayAsString: '[1,2,3]',
|
||||
objectAsString: '{"key":"value"}',
|
||||
commaInValue: 'value,with,commas',
|
||||
colonInValue: 'key:value:pair',
|
||||
braceInValue: 'value{with}braces',
|
||||
safeJsonString: 'safe_content_123'
|
||||
}
|
||||
};
|
||||
|
||||
const input = `{
|
||||
"testDataTypes": {
|
||||
"data": "{{leadingTrailingSpaces}}",
|
||||
"number": {{numberAsString}},
|
||||
"bool": {{boolAsString}},
|
||||
"array": {{arrayAsString}},
|
||||
"object": {{objectAsString}}
|
||||
},
|
||||
"testStringNumbers": {
|
||||
"stringAsNumber": "Value is {{numberAsString}}",
|
||||
"boolAsString": "Boolean: {{boolAsString}}",
|
||||
"safeContent": "{{safeJsonString}}"
|
||||
},
|
||||
"testSpecialChars": {
|
||||
"commas": "{{commaInValue}}",
|
||||
"colons": "{{colonInValue}}",
|
||||
"braces": "{{braceInValue}}"
|
||||
},
|
||||
"testMixedQuoting": {
|
||||
"inQuotes": "{{arrayAsString}}",
|
||||
"withoutQuotes": {{arrayAsString}},
|
||||
"safeQuote": "Content: {{safeJsonString}}"
|
||||
}
|
||||
}`;
|
||||
|
||||
const result = replaceJsonBodyString({ text: input }, edgeCaseProps);
|
||||
|
||||
// Should produce valid JSON
|
||||
expect(() => JSON.parse(result)).not.toThrow();
|
||||
|
||||
const parsed = JSON.parse(result);
|
||||
|
||||
// Verify trimming and data type handling
|
||||
expect(parsed.testDataTypes.data).toBe(' spaced content ');
|
||||
expect(parsed.testDataTypes.number).toBe(12345);
|
||||
expect(parsed.testDataTypes.bool).toBe(true);
|
||||
|
||||
// Verify arrays and objects are properly embedded
|
||||
expect(Array.isArray(parsed.testDataTypes.array)).toBe(true);
|
||||
expect(typeof parsed.testDataTypes.object).toBe('object');
|
||||
|
||||
// Verify special characters in values don't break JSON
|
||||
expect(parsed.testSpecialChars.commas).toContain(',');
|
||||
expect(parsed.testSpecialChars.colons).toContain(':');
|
||||
expect(parsed.testSpecialChars.braces).toContain('{');
|
||||
|
||||
// Verify string interpolation works correctly
|
||||
expect(parsed.testStringNumbers.stringAsNumber).toContain('12345');
|
||||
expect(parsed.testMixedQuoting.safeQuote).toContain('safe_content_123');
|
||||
});
|
||||
|
||||
// Test Case 3: Extreme recursion and circular reference prevention
|
||||
it('Complex Test 3: Advanced circular reference patterns and recursion limits', () => {
|
||||
const recursionProps = {
|
||||
...mockProps,
|
||||
allVariables: {
|
||||
...mockAllVariables,
|
||||
// Complex circular patterns
|
||||
chainA: '{{chainB}}',
|
||||
chainB: '{{chainC}}',
|
||||
chainC: '{{chainD}}',
|
||||
chainD: '{{chainE}}',
|
||||
chainE: '{{chainA}}', // Creates cycle
|
||||
|
||||
// Indirect self-reference
|
||||
selfIndirect: 'prefix-{{selfIndirect}}-suffix',
|
||||
|
||||
// Node reference cycles
|
||||
nodeRef1: '{{$node1.output1$}} -> {{nodeRef2}}',
|
||||
nodeRef2: '{{nodeRef1}}',
|
||||
|
||||
// Deep nested variable chains
|
||||
level1: '{{level2}}-{{level3}}',
|
||||
level2: '{{level4}}-{{level5}}',
|
||||
level3: '{{level6}}-{{level7}}',
|
||||
level4: '{{level8}}-{{level9}}',
|
||||
level5: '{{level10}}-{{level11}}',
|
||||
level6: '{{level12}}-final',
|
||||
level7: 'end',
|
||||
level8: 'deep8',
|
||||
level9: 'deep9',
|
||||
level10: 'deep10',
|
||||
level11: 'deep11',
|
||||
level12: 'deep12'
|
||||
}
|
||||
};
|
||||
|
||||
const input = `{
|
||||
"circularTests": {
|
||||
"simpleChain": "{{chainA}}",
|
||||
"selfReference": "{{selfIndirect}}",
|
||||
"nodeCircular": "{{nodeRef1}}",
|
||||
"deepNesting": "{{level1}}"
|
||||
},
|
||||
"mixedCircular": {
|
||||
"valid": "{{userName}}",
|
||||
"circular": "{{chainB}}",
|
||||
"nodeData": "{{$node1.output1$}}",
|
||||
"combined": "Valid: {{userName}}, Circular: {{chainC}}"
|
||||
}
|
||||
}`;
|
||||
|
||||
const result = replaceJsonBodyString({ text: input }, recursionProps);
|
||||
|
||||
// Should not crash or hang
|
||||
expect(result).toBeDefined();
|
||||
expect(typeof result).toBe('string');
|
||||
|
||||
// Should contain unreplaced circular references
|
||||
expect(result).toMatch(/\{\{chain[A-E]\}\}/);
|
||||
expect(result).toContain('{{selfIndirect}}');
|
||||
|
||||
// Valid variables should still be replaced
|
||||
expect(result).toContain('John Doe');
|
||||
expect(result).toContain('Hello World');
|
||||
|
||||
// Should handle deep nesting up to limit
|
||||
const parsed = JSON.parse(result);
|
||||
expect(parsed.circularTests.deepNesting).toBeDefined();
|
||||
});
|
||||
|
||||
// Test Case 4: Large payload stress test with performance validation
|
||||
it('Complex Test 4: Large payload stress test with varied data patterns', () => {
|
||||
const stressTestVariables: Record<string, any> = {};
|
||||
|
||||
// Generate large number of variables with different patterns
|
||||
for (let i = 0; i < 500; i++) {
|
||||
stressTestVariables[`str_${i}`] = `string_value_${i}_with_special_chars_"'\\\\n\\\\t`;
|
||||
stressTestVariables[`num_${i}`] = Math.random() * 1000000;
|
||||
stressTestVariables[`bool_${i}`] = i % 2 === 0;
|
||||
stressTestVariables[`obj_${i}`] = {
|
||||
id: i,
|
||||
data: `nested_${i}`,
|
||||
list: [i, i + 1, i + 2],
|
||||
nested: { deep: `value_${i}` }
|
||||
};
|
||||
stressTestVariables[`arr_${i}`] = Array.from(
|
||||
{ length: (i % 10) + 1 },
|
||||
(_, j) => `item_${i}_${j}`
|
||||
);
|
||||
}
|
||||
|
||||
// Add some problematic variables
|
||||
stressTestVariables['largeText'] = 'x'.repeat(10000);
|
||||
stressTestVariables['jsonBomb'] = JSON.stringify({ large: 'x'.repeat(1000) });
|
||||
stressTestVariables['specialChars'] = '\\n\\r\\t\\"\\\\\'`~!@#$%^&*()[]{}|;:,.<>?/+=';
|
||||
|
||||
const stressProps = {
|
||||
...mockProps,
|
||||
allVariables: { ...mockAllVariables, ...stressTestVariables }
|
||||
};
|
||||
|
||||
// Create large JSON structure
|
||||
let input = '{\n';
|
||||
for (let i = 0; i < 200; i++) {
|
||||
input += ` "section_${i}": {\n`;
|
||||
input += ` "str": "{{str_${i}}}",\n`;
|
||||
input += ` "num": {{num_${i}}},\n`;
|
||||
input += ` "bool": {{bool_${i}}},\n`;
|
||||
input += ` "obj": {{obj_${i}}},\n`;
|
||||
input += ` "arr": {{arr_${i}}},\n`;
|
||||
input += ` "mixed": "String {{str_${i}}} with number {{num_${i}}}"\n`;
|
||||
input += ` }${i < 199 ? ',' : ''}\n`;
|
||||
}
|
||||
input += '}';
|
||||
|
||||
const startTime = Date.now();
|
||||
const result = replaceJsonBodyString({ text: input }, stressProps);
|
||||
const endTime = Date.now();
|
||||
|
||||
// Performance check - should complete within reasonable time
|
||||
expect(endTime - startTime).toBeLessThan(5000); // 5 seconds max
|
||||
|
||||
// Should produce valid JSON
|
||||
expect(() => JSON.parse(result)).not.toThrow();
|
||||
|
||||
const parsed = JSON.parse(result);
|
||||
|
||||
// Verify structure integrity
|
||||
expect(Object.keys(parsed)).toHaveLength(200);
|
||||
expect(parsed.section_0.str).toContain('string_value_0');
|
||||
expect(typeof parsed.section_0.num).toBe('number');
|
||||
expect(typeof parsed.section_0.bool).toBe('boolean');
|
||||
expect(Array.isArray(parsed.section_0.arr)).toBe(true);
|
||||
});
|
||||
|
||||
// Test Case 5: Security and injection attack prevention
|
||||
it('Complex Test 5: Comprehensive security and injection attack prevention', () => {
|
||||
const securityProps = {
|
||||
...mockProps,
|
||||
allVariables: {
|
||||
...mockAllVariables,
|
||||
// SQL injection patterns
|
||||
sqlInjection: "'; DROP TABLE users; --",
|
||||
sqlInjection2: "' OR '1'='1",
|
||||
|
||||
// JSON injection attempts
|
||||
jsonBreak: '", "hacked": true, "original": "',
|
||||
jsonBreak2: '}}, "injected": {"evil": "payload"}, "fake": {{',
|
||||
|
||||
// XSS attempts
|
||||
xssScript: '<script>alert("XSS")</script>',
|
||||
xssOnload: '<img src=x onerror=alert("XSS")>',
|
||||
|
||||
// Template injection attempts
|
||||
templateInject: '{{constructor.constructor("alert(1)")()}}',
|
||||
prototypePolute: '__proto__.polluted',
|
||||
|
||||
// Path traversal
|
||||
pathTraversal: '../../../etc/passwd',
|
||||
pathTraversal2: '..\\..\\..\\windows\\system32\\config\\sam',
|
||||
|
||||
// Unicode and encoding attacks
|
||||
unicodeAttack: '\u0000\u0001\u0002\u003c\u003e',
|
||||
utf8Attack: '\uFEFF\uFFFE\uFFFF',
|
||||
|
||||
// Control characters
|
||||
controlChars: '\x00\x01\x02\x03\x04\x05\x06\x07\x08\x0E\x0F',
|
||||
|
||||
// Regex DoS attempts
|
||||
regexDos: '(' + 'a'.repeat(10000) + ')*',
|
||||
regexDos2: '(a+)+$',
|
||||
|
||||
// Large payload attack
|
||||
payloadBomb: 'A'.repeat(100000),
|
||||
|
||||
// Mixed attack vectors
|
||||
mixedAttack: '"><script>alert(/XSS/)</script><"{{malicious}}">'
|
||||
}
|
||||
};
|
||||
|
||||
const input = `{
|
||||
"sqlTests": {
|
||||
"query1": "{{sqlInjection}}",
|
||||
"query2": "SELECT * FROM users WHERE name='{{sqlInjection2}}'"
|
||||
},
|
||||
"jsonTests": {
|
||||
"break1": "{{jsonBreak}}",
|
||||
"break2": "{{jsonBreak2}}",
|
||||
"safe": "normal_value"
|
||||
},
|
||||
"xssTests": {
|
||||
"script": "{{xssScript}}",
|
||||
"onload": "{{xssOnload}}",
|
||||
"content": "<div>{{xssScript}}</div>"
|
||||
},
|
||||
"templateTests": {
|
||||
"inject": "{{templateInject}}",
|
||||
"prototype": "{{prototypePolute}}"
|
||||
},
|
||||
"pathTests": {
|
||||
"traversal1": "{{pathTraversal}}",
|
||||
"traversal2": "{{pathTraversal2}}"
|
||||
},
|
||||
"encodingTests": {
|
||||
"unicode": "{{unicodeAttack}}",
|
||||
"utf8": "{{utf8Attack}}",
|
||||
"control": "{{controlChars}}"
|
||||
},
|
||||
"performanceTests": {
|
||||
"regex1": "{{regexDos}}",
|
||||
"regex2": "{{regexDos2}}",
|
||||
"large": "{{payloadBomb}}"
|
||||
},
|
||||
"mixedAttack": "{{mixedAttack}}"
|
||||
}`;
|
||||
|
||||
const startTime = Date.now();
|
||||
const result = replaceJsonBodyString({ text: input }, securityProps);
|
||||
const endTime = Date.now();
|
||||
|
||||
// Should not take excessive time (DoS protection)
|
||||
expect(endTime - startTime).toBeLessThan(10000); // 10 seconds max
|
||||
|
||||
// Should produce valid JSON despite malicious input
|
||||
expect(() => JSON.parse(result)).not.toThrow();
|
||||
|
||||
const parsed = JSON.parse(result);
|
||||
|
||||
// Verify dangerous content is properly handled
|
||||
expect(parsed.sqlTests.query1).toBe("'; DROP TABLE users; --");
|
||||
expect(parsed.xssTests.script).toBe('<script>alert("XSS")</script>'); // Content is preserved as-is in JSON string
|
||||
|
||||
// The function properly escapes strings even with injection attempts
|
||||
expect(typeof parsed.jsonTests.break1).toBe('string'); // Should be a string, not break JSON structure
|
||||
expect(parsed.jsonTests.break1).toBe('", "hacked": true, "original": "'); // The injection content is escaped
|
||||
|
||||
// Verify the JSON structure wasn't broken by injection attempts
|
||||
expect(Object.keys(parsed)).toEqual([
|
||||
'sqlTests',
|
||||
'jsonTests',
|
||||
'xssTests',
|
||||
'templateTests',
|
||||
'pathTests',
|
||||
'encodingTests',
|
||||
'performanceTests',
|
||||
'mixedAttack'
|
||||
]);
|
||||
|
||||
// Verify large payloads are handled
|
||||
expect(parsed.performanceTests.large).toHaveLength(100000);
|
||||
|
||||
// Verify no code execution occurred (template injection variable was replaced with null because it doesn't exist)
|
||||
expect(typeof parsed.templateTests.inject).toBe('string');
|
||||
expect(parsed.templateTests.inject).toBe('null'); // Non-existent variables become "null"
|
||||
});
|
||||
});
|
||||
});
|
29
test/tsconfig.json
Normal file
29
test/tsconfig.json
Normal file
@@ -0,0 +1,29 @@
|
||||
{
|
||||
"extends": "../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"baseUrl": "..",
|
||||
"paths": {
|
||||
"@fastgpt/*": ["packages/*"],
|
||||
"@test/*": ["test/*"],
|
||||
"@/*": ["projects/app/src/*"]
|
||||
},
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"allowSyntheticDefaultImports": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"noImplicitAny": false,
|
||||
"types": ["vitest/globals", "node"]
|
||||
},
|
||||
"include": [
|
||||
"**/*.ts",
|
||||
"**/*.tsx",
|
||||
"../packages/**/*.ts",
|
||||
"../packages/**/*.tsx",
|
||||
"../packages/**/*.d.ts",
|
||||
"../projects/app/src/**/*.ts",
|
||||
"../projects/app/src/**/*.tsx"
|
||||
],
|
||||
"exclude": ["node_modules", "../node_modules", "dist", "../dist"]
|
||||
}
|
Reference in New Issue
Block a user