Skip to content

Commit de73d43

Browse files
committed
fix(knowledge): reject impossible calendar dates in tag filters
Greptile P1: the date value check was format-only, so 2026-02-30 / 2026-99-99 passed the boundary and then made the document query's ::date cast throw a 500. Validate real calendar dates by round-tripping the parsed parts.
1 parent 5f4fe13 commit de73d43

2 files changed

Lines changed: 22 additions & 1 deletion

File tree

apps/sim/lib/api/contracts/knowledge/documents.test.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,14 @@ describe('parseDocumentTagFiltersParam', () => {
9393
JSON.stringify([{ tagSlot: 'date1', fieldType: 'date', operator: 'eq', value: 'nope' }])
9494
)
9595
).toThrow()
96+
// well-formed but impossible calendar dates (would 500 on ::date)
97+
for (const value of ['2026-02-30', '2026-99-99', '2026-13-01', '2026-00-10']) {
98+
expect(() =>
99+
parseDocumentTagFiltersParam(
100+
JSON.stringify([{ tagSlot: 'date1', fieldType: 'date', operator: 'eq', value }])
101+
)
102+
).toThrow()
103+
}
96104
// non-boolean value on a boolean field
97105
expect(() =>
98106
parseDocumentTagFiltersParam(

apps/sim/lib/knowledge/filters/types.ts

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,19 @@ export function getOperatorsForFieldType(fieldType: FilterFieldType): OperatorIn
193193
/** Wire format for a date filter value (`YYYY-MM-DD`). */
194194
const DATE_ONLY_VALUE = /^\d{4}-\d{2}-\d{2}$/
195195

196+
/**
197+
* Whether a `YYYY-MM-DD` string is a real calendar date. The format regex alone
198+
* still admits impossible dates (`2026-02-30`, `2026-99-99`) that pass the
199+
* boundary and then make the document query's `::date` cast throw a 500; this
200+
* round-trips the parsed parts to reject them.
201+
*/
202+
function isRealCalendarDate(value: string): boolean {
203+
if (!DATE_ONLY_VALUE.test(value)) return false
204+
const [year, month, day] = value.split('-').map(Number)
205+
const date = new Date(year, month - 1, day)
206+
return date.getFullYear() === year && date.getMonth() === month - 1 && date.getDate() === day
207+
}
208+
196209
/**
197210
* Whether a raw filter value is usable for the given field type. Shared source
198211
* of truth so the API boundary can reject unusable values (e.g. `"abc"` for a
@@ -208,7 +221,7 @@ export function isValidFilterValue(fieldType: FilterFieldType, value: unknown):
208221
if (typeof value === 'number') return Number.isFinite(value)
209222
return typeof value === 'string' && value.trim() !== '' && Number.isFinite(Number(value))
210223
case 'date':
211-
return typeof value === 'string' && DATE_ONLY_VALUE.test(value)
224+
return typeof value === 'string' && isRealCalendarDate(value)
212225
case 'boolean':
213226
return (
214227
typeof value === 'boolean' ||

0 commit comments

Comments
 (0)