From 15adf09b73df06126b171e216ed555a54c4135fa Mon Sep 17 00:00:00 2001 From: Marko Djordjevic Date: Wed, 18 Feb 2026 11:19:26 +0100 Subject: [PATCH] fix: add parseInt(value, 10) with isNaN() guards to all integer query param parsing - Add radix 10 to all parseInt() calls parsing integer query/path parameters - Add isNaN() guards returning HTTP 400 with descriptive error messages - Updated routes: annotations, candles, export, export/spans, annotation-types/[id], span-annotations, span-annotations/[id], span-label-types/[id] - Ensures strict integer parsing and prevents invalid parameter values from reaching database queries --- src/app/api/annotation-types/[id]/route.ts | 11 ++++++++- src/app/api/annotations/[id]/route.ts | 4 ++-- src/app/api/annotations/route.ts | 28 +++++++++++++++++++--- src/app/api/candles/route.ts | 10 +++++++- src/app/api/export/route.ts | 7 ++++++ src/app/api/export/spans/route.ts | 13 ++++++++++ src/app/api/span-annotations/[id]/route.ts | 25 +++++++++++++++---- src/app/api/span-annotations/route.ts | 18 ++++++++++++-- src/app/api/span-label-types/[id]/route.ts | 25 +++++++++++++++---- 9 files changed, 124 insertions(+), 17 deletions(-) diff --git a/src/app/api/annotation-types/[id]/route.ts b/src/app/api/annotation-types/[id]/route.ts index 4bf4721..73fbb1e 100644 --- a/src/app/api/annotation-types/[id]/route.ts +++ b/src/app/api/annotation-types/[id]/route.ts @@ -14,6 +14,15 @@ export async function PATCH( ) { try { const { id } = await context.params; + const idNum = parseInt(id, 10); + + if (isNaN(idNum)) { + return NextResponse.json( + { error: 'Invalid parameter: id must be an integer' }, + { status: 400 } + ); + } + const body = await request.json(); const { name, display_name, color, category, icon, is_active } = body; @@ -36,7 +45,7 @@ export async function PATCH( const result = await db .update(annotationTypes) .set(updateData) - .where(eq(annotationTypes.id, parseInt(id))) + .where(eq(annotationTypes.id, idNum)) .returning(); if (result.length === 0) { diff --git a/src/app/api/annotations/[id]/route.ts b/src/app/api/annotations/[id]/route.ts index fd276b7..f2274c7 100644 --- a/src/app/api/annotations/[id]/route.ts +++ b/src/app/api/annotations/[id]/route.ts @@ -9,7 +9,7 @@ export async function PATCH( ) { try { const { id: idParam } = await params; - const id = parseInt(idParam); + const id = parseInt(idParam, 10); if (isNaN(id)) { return NextResponse.json( @@ -57,7 +57,7 @@ export async function DELETE( ) { try { const { id: idParam } = await params; - const id = parseInt(idParam); + const id = parseInt(idParam, 10); if (isNaN(id)) { return NextResponse.json( diff --git a/src/app/api/annotations/route.ts b/src/app/api/annotations/route.ts index 3d87f50..e50abcb 100644 --- a/src/app/api/annotations/route.ts +++ b/src/app/api/annotations/route.ts @@ -18,10 +18,18 @@ export async function GET(request: NextRequest) { chartId = String(latest[0].id); } + const chartIdNum = parseInt(chartId, 10); + if (isNaN(chartIdNum)) { + return NextResponse.json( + { error: 'Invalid parameter: chartId must be an integer' }, + { status: 400 } + ); + } + const allAnnotations = await db .select() .from(annotations) - .where(eq(annotations.chart_id, parseInt(chartId, 10))); + .where(eq(annotations.chart_id, chartIdNum)); const normalized = allAnnotations.map((a) => ({ ...a, @@ -108,13 +116,27 @@ export async function DELETE(request: NextRequest) { { status: 400 } ); } - result = await db.delete(annotations).where(eq(annotations.chart_id, parseInt(chartId, 10))).returning(); + const chartIdNum = parseInt(chartId, 10); + if (isNaN(chartIdNum)) { + return NextResponse.json( + { error: 'Invalid parameter: chartId must be an integer' }, + { status: 400 } + ); + } + result = await db.delete(annotations).where(eq(annotations.chart_id, chartIdNum)).returning(); } else if (type) { const types = type.split(',').map((t) => t.trim()); if (chartId) { + const chartIdNum = parseInt(chartId, 10); + if (isNaN(chartIdNum)) { + return NextResponse.json( + { error: 'Invalid parameter: chartId must be an integer' }, + { status: 400 } + ); + } result = await db .delete(annotations) - .where(and(inArray(annotations.label_type, types), eq(annotations.chart_id, parseInt(chartId, 10)))) + .where(and(inArray(annotations.label_type, types), eq(annotations.chart_id, chartIdNum))) .returning(); } else { result = await db diff --git a/src/app/api/candles/route.ts b/src/app/api/candles/route.ts index 4429a59..ee2d3b6 100644 --- a/src/app/api/candles/route.ts +++ b/src/app/api/candles/route.ts @@ -17,6 +17,14 @@ export async function GET(request: NextRequest) { chartId = String(latest[0].id); } + const chartIdNum = parseInt(chartId, 10); + if (isNaN(chartIdNum)) { + return NextResponse.json( + { error: 'Invalid parameter: chartId must be an integer' }, + { status: 400 } + ); + } + const allCandles = await db .select({ time: candles.time, @@ -26,7 +34,7 @@ export async function GET(request: NextRequest) { close: candles.close, }) .from(candles) - .where(eq(candles.chart_id, parseInt(chartId, 10))) + .where(eq(candles.chart_id, chartIdNum)) .orderBy(asc(candles.time)); const normalized = allCandles.map((c) => ({ diff --git a/src/app/api/export/route.ts b/src/app/api/export/route.ts index d29746e..74355d5 100644 --- a/src/app/api/export/route.ts +++ b/src/app/api/export/route.ts @@ -23,6 +23,13 @@ export async function GET(request: NextRequest) { } const chartIdNum = parseInt(chartId, 10); + if (isNaN(chartIdNum)) { + return NextResponse.json( + { error: 'Invalid parameter: chartId must be an integer' }, + { status: 400 } + ); + } + const allAnnotations = await db .select() .from(annotations) diff --git a/src/app/api/export/spans/route.ts b/src/app/api/export/spans/route.ts index 0801143..a008814 100644 --- a/src/app/api/export/spans/route.ts +++ b/src/app/api/export/spans/route.ts @@ -19,6 +19,19 @@ export async function GET(request: NextRequest) { } const chartIdNum = parseInt(chartId, 10); + if (isNaN(chartIdNum)) { + return NextResponse.json( + { error: 'Invalid parameter: chartId must be an integer' }, + { status: 400 } + ); + } + + if (isNaN(contextPadding)) { + return NextResponse.json( + { error: 'Invalid parameter: context_padding must be an integer' }, + { status: 400 } + ); + } // Fetch span annotations for the chart const spans = await db diff --git a/src/app/api/span-annotations/[id]/route.ts b/src/app/api/span-annotations/[id]/route.ts index 9d64d32..8ce9baa 100644 --- a/src/app/api/span-annotations/[id]/route.ts +++ b/src/app/api/span-annotations/[id]/route.ts @@ -10,6 +10,15 @@ export async function PATCH( ) { try { const { id } = await params; + const idNum = parseInt(id, 10); + + if (isNaN(idNum)) { + return NextResponse.json( + { error: 'Invalid parameter: id must be an integer' }, + { status: 400 } + ); + } + const body = await request.json(); const { label, confidence, outcome, notes, sub_spans } = body; @@ -18,7 +27,7 @@ export async function PATCH( const existing = await db .select() .from(spanAnnotations) - .where(eq(spanAnnotations.id, parseInt(id))) + .where(eq(spanAnnotations.id, idNum)) .limit(1); if (existing.length === 0) { @@ -39,7 +48,7 @@ export async function PATCH( const result = await db .update(spanAnnotations) .set(updates) - .where(eq(spanAnnotations.id, parseInt(id))) + .where(eq(spanAnnotations.id, idNum)) .returning(); const s = result[0]; @@ -65,12 +74,20 @@ export async function DELETE( ) { try { const { id } = await params; + const idNum = parseInt(id, 10); + + if (isNaN(idNum)) { + return NextResponse.json( + { error: 'Invalid parameter: id must be an integer' }, + { status: 400 } + ); + } // Check if the span exists const existing = await db .select() .from(spanAnnotations) - .where(eq(spanAnnotations.id, parseInt(id))) + .where(eq(spanAnnotations.id, idNum)) .limit(1); if (existing.length === 0) { @@ -80,7 +97,7 @@ export async function DELETE( ); } - await db.delete(spanAnnotations).where(eq(spanAnnotations.id, parseInt(id))); + await db.delete(spanAnnotations).where(eq(spanAnnotations.id, idNum)); return NextResponse.json({ message: 'Span annotation deleted successfully' }); } catch (error) { diff --git a/src/app/api/span-annotations/route.ts b/src/app/api/span-annotations/route.ts index 81d37bd..b1ec261 100644 --- a/src/app/api/span-annotations/route.ts +++ b/src/app/api/span-annotations/route.ts @@ -16,10 +16,18 @@ export async function GET(request: NextRequest) { ); } + const chartIdNum = parseInt(chartId, 10); + if (isNaN(chartIdNum)) { + return NextResponse.json( + { error: 'Invalid parameter: chartId must be an integer' }, + { status: 400 } + ); + } + const spans = await db .select() .from(spanAnnotations) - .where(eq(spanAnnotations.chart_id, parseInt(chartId))) + .where(eq(spanAnnotations.chart_id, chartIdNum)) .orderBy(desc(spanAnnotations.start_time)); const normalized = spans.map((s) => ({ @@ -116,7 +124,13 @@ export async function DELETE(request: NextRequest) { ); } - const chartIdInt = parseInt(chartId); + const chartIdInt = parseInt(chartId, 10); + if (isNaN(chartIdInt)) { + return NextResponse.json( + { error: 'Invalid parameter: chartId must be an integer' }, + { status: 400 } + ); + } // Build filter conditions const conditions = [eq(spanAnnotations.chart_id, chartIdInt)]; diff --git a/src/app/api/span-label-types/[id]/route.ts b/src/app/api/span-label-types/[id]/route.ts index 0f65e8f..09b0893 100644 --- a/src/app/api/span-label-types/[id]/route.ts +++ b/src/app/api/span-label-types/[id]/route.ts @@ -10,6 +10,15 @@ export async function PATCH( ) { try { const { id } = await params; + const idNum = parseInt(id, 10); + + if (isNaN(idNum)) { + return NextResponse.json( + { error: 'Invalid parameter: id must be an integer' }, + { status: 400 } + ); + } + const body = await request.json(); const { name, display_name, color, hotkey, is_active, sort_order } = body; @@ -18,7 +27,7 @@ export async function PATCH( const existing = await db .select() .from(spanLabelTypes) - .where(eq(spanLabelTypes.id, parseInt(id))) + .where(eq(spanLabelTypes.id, idNum)) .limit(1); if (existing.length === 0) { @@ -40,7 +49,7 @@ export async function PATCH( const result = await db .update(spanLabelTypes) .set(updates) - .where(eq(spanLabelTypes.id, parseInt(id))) + .where(eq(spanLabelTypes.id, idNum)) .returning(); return NextResponse.json(result[0]); @@ -68,12 +77,20 @@ export async function DELETE( ) { try { const { id } = await params; + const idNum = parseInt(id, 10); + + if (isNaN(idNum)) { + return NextResponse.json( + { error: 'Invalid parameter: id must be an integer' }, + { status: 400 } + ); + } // Check if the type exists const type = await db .select() .from(spanLabelTypes) - .where(eq(spanLabelTypes.id, parseInt(id))) + .where(eq(spanLabelTypes.id, idNum)) .limit(1); if (type.length === 0) { @@ -97,7 +114,7 @@ export async function DELETE( ); } - await db.delete(spanLabelTypes).where(eq(spanLabelTypes.id, parseInt(id))); + await db.delete(spanLabelTypes).where(eq(spanLabelTypes.id, idNum)); return NextResponse.json({ message: 'Span label type deleted successfully' }); } catch (error) {