ReDoS (CWE-1333): Bad backtracking in `like()`/`ilike()` query LIKE pattern compilation
Los mantenedores suelen responder en 1 día
Nadie ha tomado este issue todavía.
Evaluación
- Dificultad
- 3/5
- Tiempo estimado
- 1-2 días
- Aptitud para principiantes
- 64/100
- Tipo de issue
- Error
- Claridad
- Bastante claro
- Estado de actividad
- Tranquilo
- Stack tecnológico
- typescript
Línea de trabajo
Comienza en packages/db/src/query/compiler/evaluators.ts, en evaluateLike(), y reproduce el patrón de casi coincidencia proporcionado para confirmar la ralentización. Revisa la conversión de comodines y conserva el comportamiento existente de LIKE/ILIKE, asegurándote al mismo tiempo de que los patrones diseñados no puedan desencadenar un backtracking catastrófico; se considera terminado cuando el PoC se completa rápidamente y el comportamiento de coincidencia sigue cubierto por pruebas.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
The problem
The like() and ilike() query builder functions convert SQL LIKE wildcards (% → .*) into a RegExp without bounds. A crafted pattern with many % wildcards creates overlapping .* segments that cause catastrophic backtracking on near-matching inputs.
Vulnerable code
File: packages/db/src/query/compiler/evaluators.ts (lines 632-655)
function evaluateLike(
value: any,
pattern: any,
caseInsensitive: boolean,
): boolean {
if (typeof value !== `string` || typeof pattern !== `string`) {
return false
}
const searchValue = caseInsensitive ? value.toLowerCase() : value
const searchPattern = caseInsensitive ? pattern.toLowerCase() : pattern
// Convert SQL LIKE pattern to regex
// First escape all regex special chars except % and _
let regexPattern = searchPattern.replace(/[.*+?^${}()|[\]\\]/g, `\\$&`)
// Then convert SQL wildcards to regex
regexPattern = regexPattern.replace(/%/g, `.*`) // % matches any sequence
regexPattern = regexPattern.replace(/_/g, `.`) // _ matches any single char
// 's' (dotAll flag) makes '.' match all characters including line terminations
const regex = new RegExp(`^${regexPattern}$`, 's')
return regex.test(searchValue)
}
PoC
import { createCollection, createLiveQueryCollection, like } from '@tanstack/db'
const collection = createCollection({
name: 'items',
schema: { name: 'string' },
sync: { type: 'none' },
})
const liveQuery = createLiveQueryCollection({
source: collection,
query: (q) => q.where(({ item }) => like(item.name, 'a%a%a%a%a%a%a%a%a%a%a%a%a%a%a%a%a%a%a%a')),
})
// Near-miss: long string that matches most of the pattern but fails at the end
// The 'b' at the end forces the engine into exponential backtracking
const nearMiss = 'a' + 'a'.repeat(50) + 'b'
const start = Date.now()
await liveQuery.refetch()
console.log(`Took ${Date.now() - start}ms`) // seconds to minutes
Why it's exploitable
like()/ilike()are part of the public query builder API, any app using TanStack/db can call them- The pattern string is accepted as
StringLike(barestring) with zero validation - Each
%becomes.*, creating N+1 overlapping unbounded quantifier segments - When tested against a near-miss string (long, matches most of the pattern but fails near the end), the regex engine tries all possible ways to distribute the input across the segments → exponential time
Remediation
Quick fix : limit wildcards in evaluateLike():
const MAX_WILDCARDS = 20
if ((pattern as string).match(/%/g)?.length > MAX_WILDCARDS) {
throw new Error('LIKE pattern contains too many wildcards')
}
Long-term : replace regex-based LIKE with a manual character-by-character matcher (e.g. KMP-style) for O(n*m) worst-case.
Submitting
Per TanStack's security advisory process, I'd like to submit a GHSA for this. Happy to provide a more detailed report or PoC video if needed.
- Lenguaje dominante
- TypeScript
- Estrellas
- 3.9k
- Forks
- 272
- Merge medio
- 1 d 45 min
- PR fusionados (30 d)
- 208
Preparar el entorno
- Sin Dockerfile ni archivo de Docker Compose
- Tiene una plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de TanStack/db
-
Dificultad 5/5 Más de una semana Aptitud para principiantes 5/100
Los mantenedores suelen responder en 1 día
-
browser-db-sqlite-persistence: option to load the wa-sqlite WASM by URL instead of inlined base64Posiblemente ocupada @KyleAMathews la tomó hoy. Abierto
Dificultad 4/5 3-5 días Aptitud para principiantes 35/100
TanStack/db#2101 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 4/5 3-5 días Aptitud para principiantes 45/100
Los mantenedores suelen responder en 1 día
-
Dificultad 3/5 1-2 días Aptitud para principiantes 72/100
Los mantenedores suelen responder en 1 día
-
Dificultad 4/5 3-5 días Aptitud para principiantes 48/100
TanStack/db#1972 · 2 comentarios ·
Los mantenedores suelen responder en 1 día
Todos los issues de TanStack/db
Issues similares
-
area:deploy task
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
wilanis/wilanis-js#1017 ·
Los mantenedores suelen responder en 1 día
-
documentation v2
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
modelcontextprotocol/servers#5133 ·
Los mantenedores suelen responder en 1 día
-
budget bug feature frontend good-issue medium needs-review OSCI'26 payments
Dificultad 2/5 1-3 horas Aptitud para principiantes 75/100
SatyamPandey-07/WorkSphere#5990 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
lugassawan/swe-workbench#799 ·
Los mantenedores suelen responder en 1 día
-
bug good first issue javascript
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
yukikotani231/sqlsift#326 ·
Los mantenedores suelen responder en 1 día