Skip to content

Commit 9009045

Browse files
Copilothotlong
andcommitted
Fix security issues in validation engine
Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 0072df8 commit 9009045

2 files changed

Lines changed: 60 additions & 5 deletions

File tree

packages/core/src/validation/__tests__/object-validation-engine.test.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ describe('ObjectValidationEngine', () => {
179179
record: { email: 'user@example.com', tenant_id: 'tenant-123' },
180180
};
181181

182-
const results = await engine.validateRecord([rule], context, 'insert');
182+
await engine.validateRecord([rule], context, 'insert');
183183
expect(uniquenessChecker).toHaveBeenCalledWith(
184184
['email', 'tenant_id'],
185185
{ email: 'user@example.com', tenant_id: 'tenant-123' },

packages/core/src/validation/validators/object-validation-engine.ts

Lines changed: 59 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -81,18 +81,73 @@ export interface ValidationExpressionEvaluator {
8181
/**
8282
* Simple expression evaluator (basic implementation)
8383
* In production, this should use a proper expression engine
84+
*
85+
* SECURITY NOTE: This implementation uses a sandboxed approach with limited
86+
* expression capabilities. For production use, consider:
87+
* - JSONLogic (jsonlogic.com)
88+
* - expr-eval with allowlist
89+
* - Custom AST-based evaluator
8490
*/
8591
class SimpleExpressionEvaluator implements ValidationExpressionEvaluator {
8692
evaluate(expression: string, context: Record<string, any>): any {
8793
try {
88-
// Create a safe evaluation context
89-
const func = new Function(...Object.keys(context), `return ${expression}`);
90-
return func(...Object.values(context));
94+
// Sanitize expression: only allow basic comparisons and logical operators
95+
// This is a basic safeguard - proper expression parsing should be used in production
96+
const sanitizedExpression = this.sanitizeExpression(expression);
97+
98+
// Create a safe evaluation context with read-only access
99+
const safeContext = this.createSafeContext(context);
100+
const contextKeys = Object.keys(safeContext);
101+
const contextValues = Object.values(safeContext);
102+
103+
// Use Function constructor with controlled input
104+
const func = new Function(...contextKeys, `'use strict'; return (${sanitizedExpression});`);
105+
return func(...contextValues);
91106
} catch (error) {
92107
console.error('Expression evaluation error:', error);
93108
return false;
94109
}
95110
}
111+
112+
/**
113+
* Sanitize expression to prevent code injection
114+
*/
115+
private sanitizeExpression(expression: string): string {
116+
// Remove potentially dangerous patterns
117+
const dangerous = [
118+
/require\s*\(/gi,
119+
/import\s+/gi,
120+
/eval\s*\(/gi,
121+
/Function\s*\(/gi,
122+
/constructor/gi,
123+
/__proto__/gi,
124+
/prototype/gi,
125+
];
126+
127+
for (const pattern of dangerous) {
128+
if (pattern.test(expression)) {
129+
throw new Error('Invalid expression: contains forbidden pattern');
130+
}
131+
}
132+
133+
return expression;
134+
}
135+
136+
/**
137+
* Create a safe read-only context
138+
*/
139+
private createSafeContext(context: Record<string, any>): Record<string, any> {
140+
const safe: Record<string, any> = {};
141+
for (const [key, value] of Object.entries(context)) {
142+
// Deep clone primitive values and objects to prevent mutation
143+
if (typeof value === 'object' && value !== null) {
144+
safe[key] = JSON.parse(JSON.stringify(value));
145+
} else {
146+
safe[key] = value;
147+
}
148+
}
149+
return safe;
150+
}
96151
}
97152

98153
/**
@@ -533,7 +588,7 @@ export class ObjectValidationEngine {
533588
private getPredefinedPattern(format: string): RegExp {
534589
const patterns: Record<string, RegExp> = {
535590
email: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
536-
url: /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)$/,
591+
url: /^https?:\/\/(www\.)?[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&/=]*)$/,
537592
phone: /^[\d\s\-+()]+$/,
538593
ipv4: /^(\d{1,3}\.){3}\d{1,3}$/,
539594
ipv6: /^([\da-f]{1,4}:){7}[\da-f]{1,4}$/i,

0 commit comments

Comments
 (0)