Skip to content

Commit e5cf85e

Browse files
Copilothotlong
andcommitted
Security fix: Escape regex characters and remove dead code
- Add escapeRegex() helper to prevent ReDoS attacks - Escape special regex characters in contains/startswith/endswith operators - Remove unused buildSortObject() method (manual sort is used instead) - Prevent regex injection vulnerabilities in query filters Co-authored-by: hotlong <50353452+hotlong@users.noreply.github.com>
1 parent 11b961f commit e5cf85e

2 files changed

Lines changed: 124 additions & 26 deletions

File tree

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
/**
2+
* Demonstration of Mingo Integration in Memory Driver
3+
*
4+
* This file shows how ObjectQL filters are converted to MongoDB queries
5+
* and processed by Mingo for in-memory data.
6+
*/
7+
8+
// Example filter conversions:
9+
10+
/**
11+
* Example 1: Simple Equality
12+
*
13+
* ObjectQL Filter:
14+
* [['role', '=', 'admin']]
15+
*
16+
* Converts to MongoDB Query:
17+
* { role: 'admin' }
18+
*/
19+
20+
/**
21+
* Example 2: Comparison Operators
22+
*
23+
* ObjectQL Filter:
24+
* [['age', '>', 30]]
25+
*
26+
* Converts to MongoDB Query:
27+
* { age: { $gt: 30 } }
28+
*/
29+
30+
/**
31+
* Example 3: OR Logic
32+
*
33+
* ObjectQL Filter:
34+
* [
35+
* ['role', '=', 'admin'],
36+
* 'or',
37+
* ['age', '>', 30]
38+
* ]
39+
*
40+
* Converts to MongoDB Query:
41+
* {
42+
* $or: [
43+
* { role: 'admin' },
44+
* { age: { $gt: 30 } }
45+
* ]
46+
* }
47+
*/
48+
49+
/**
50+
* Example 4: AND Logic (Multiple Conditions)
51+
*
52+
* ObjectQL Filter:
53+
* [
54+
* ['status', '=', 'active'],
55+
* 'and',
56+
* ['role', '=', 'user']
57+
* ]
58+
*
59+
* Converts to MongoDB Query:
60+
* {
61+
* $and: [
62+
* { status: 'active' },
63+
* { role: 'user' }
64+
* ]
65+
* }
66+
*/
67+
68+
/**
69+
* Example 5: String Contains (Case-Insensitive)
70+
*
71+
* ObjectQL Filter:
72+
* [['name', 'contains', 'john']]
73+
*
74+
* Converts to MongoDB Query:
75+
* { name: { $regex: /john/i } }
76+
*/
77+
78+
/**
79+
* Example 6: IN Operator
80+
*
81+
* ObjectQL Filter:
82+
* [['status', 'in', ['active', 'pending']]]
83+
*
84+
* Converts to MongoDB Query:
85+
* { status: { $in: ['active', 'pending'] } }
86+
*/
87+
88+
/**
89+
* Example 7: Between Range
90+
*
91+
* ObjectQL Filter:
92+
* [['age', 'between', [25, 35]]]
93+
*
94+
* Converts to MongoDB Query:
95+
* { age: { $gte: 25, $lte: 35 } }
96+
*/
97+
98+
/**
99+
* Implementation Details:
100+
*
101+
* The MemoryDriver now uses:
102+
*
103+
* 1. convertToMongoQuery(filters) - Converts ObjectQL filters to MongoDB query
104+
* 2. new Query(mongoQuery) - Creates a Mingo query instance
105+
* 3. query.find(records).all() - Executes query and returns matching records
106+
*
107+
* This provides:
108+
* - MongoDB-compatible query semantics
109+
* - High performance in-memory queries
110+
* - Rich operator support
111+
* - Consistent behavior with MongoDB
112+
*
113+
* All while maintaining 100% backward compatibility with existing ObjectQL code!
114+
*/
115+
116+
console.log('Mingo Integration Demo - See comments in file for query conversion examples');

packages/drivers/memory/src/index.ts

Lines changed: 8 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -618,15 +618,16 @@ export class MemoryDriver implements Driver {
618618
case 'contains':
619619
case 'like':
620620
// MongoDB regex for case-insensitive contains
621-
return { [field]: { $regex: new RegExp(value, 'i') } };
621+
// Escape special regex characters to prevent ReDoS and ensure literal matching
622+
return { [field]: { $regex: new RegExp(this.escapeRegex(value), 'i') } };
622623

623624
case 'startswith':
624625
case 'starts_with':
625-
return { [field]: { $regex: new RegExp(`^${value}`, 'i') } };
626+
return { [field]: { $regex: new RegExp(`^${this.escapeRegex(value)}`, 'i') } };
626627

627628
case 'endswith':
628629
case 'ends_with':
629-
return { [field]: { $regex: new RegExp(`${value}$`, 'i') } };
630+
return { [field]: { $regex: new RegExp(`${this.escapeRegex(value)}$`, 'i') } };
630631

631632
case 'between':
632633
if (Array.isArray(value) && value.length === 2) {
@@ -643,30 +644,11 @@ export class MemoryDriver implements Driver {
643644
}
644645

645646
/**
646-
* Build MongoDB sort object from ObjectQL sort array.
647-
* Converts [['field', 'asc'], ['field2', 'desc']] to { field: 1, field2: -1 }
647+
* Escape special regex characters to prevent ReDoS and ensure literal matching.
648+
* This is crucial for security when using user input in regex patterns.
648649
*/
649-
private buildSortObject(sort: any[]): Record<string, any> {
650-
const sortObj: Record<string, any> = {};
651-
652-
for (const sortItem of sort) {
653-
let field: string;
654-
let direction: string;
655-
656-
if (Array.isArray(sortItem)) {
657-
[field, direction] = sortItem;
658-
} else if (typeof sortItem === 'object') {
659-
field = sortItem.field;
660-
direction = sortItem.order || sortItem.direction || sortItem.dir || 'asc';
661-
} else {
662-
continue;
663-
}
664-
665-
// MongoDB uses 1 for ascending, -1 for descending
666-
sortObj[field] = direction.toLowerCase() === 'desc' ? -1 : 1;
667-
}
668-
669-
return sortObj;
650+
private escapeRegex(str: string): string {
651+
return String(str).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
670652
}
671653

672654
/**

0 commit comments

Comments
 (0)