Summary
[!IMPORTANT]
Only instances running on the SQLite driver (better-sqlite3) are affected; SQLite is usually used in development/testing backend, so production deployments on PostgreSQL or MySQL/MariaDB are unaffected.
The StringOperators.regex filter exposed on the public Shop GraphQL API is evaluated inside the Node.js event loop via a synchronous SQLite user-defined function (UDF). Supplying a catastrophically backtracking pattern blocks the entire event loop, causing a complete denial of service with no authentication required.
Details
Vendure registers a JavaScript UDF so that SQLite can handle the REGEXP operator:
packages/core/src/service/helpers/list-query-builder/list-query-builder.ts lines 917–931
private registerSQLiteRegexpFunction() {
const regexpFn = (pattern: string, value: string) => {
const result = new RegExp(`${pattern}`, 'i').test(value); // user-controlled pattern
return result ? 1 : 0;
};
if (dbType === 'better-sqlite3') {
driver.databaseConnection.function('regexp', regexpFn);
}
if (dbType === 'sqljs') {
driver.databaseConnection.create_function('regexp', regexpFn);
}
}
The pattern argument is the raw value of StringOperators.regex submitted by the caller. No length limit, timeout, or safe-regex validation is applied before constructing new RegExp(pattern).
packages/core/src/service/helpers/list-query-builder/parse-filter-params.ts lines 321–325
case 'regex':
return {
clause: getRegexpClause(fieldName, argIndex, dbType),
parameters: { [`arg${argIndex}`]: operand }, // operand = raw user input
};
The products resolver in packages/core/src/api/resolvers/shop/shop-products.resolver.ts carries no @Allow decorator, and the access control strategy treats an empty permission set as publicly accessible:
<img width="1610" height="458" alt="image" src="https://github.com/user-attachments/assets/327f3847-79d9-43e2-859e-47a753e61b2b" />
[poc.zip](https://github.com/user-attachments/files/28752976/poc.zip)
[poc-redos.js](https://github.com/user-attachments/files/28753000/poc-redos.js)