security: add CSV injection protection to all export routes
Add sanitizeCsvCell() helper to both export routes that prefixes cell values starting with =, +, @, or - with a single quote to prevent CSV formula injection attacks. Applied to: - src/app/api/export/route.ts: timestamp and label_type columns - src/app/api/span-annotations/export/route.ts: start_time, end_time, label, and outcome columns Closes task 4.10. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
160f146ab4
commit
b2129ad626
3 changed files with 18 additions and 4 deletions
|
|
@ -35,7 +35,7 @@
|
|||
- [x] 4.7 `[sonnet]` Require `chartId` for bulk delete in `src/app/api/annotations/route.ts` — reject `?all=true` without chartId with HTTP 400
|
||||
- [x] 4.8 `[sonnet]` Wrap chart cascade delete in `db.transaction()` and add `spanAnnotations` deletion in `src/app/api/charts/[id]/route.ts`
|
||||
- [x] 4.9 `[haiku]` Add `parseInt(value, 10)` with `isNaN()` guard to all routes parsing integer query params
|
||||
- [ ] 4.10 `[sonnet]` Add CSV injection protection (prefix `=+@-` cells with `'`) to all export routes
|
||||
- [x] 4.10 `[sonnet]` Add CSV injection protection (prefix `=+@-` cells with `'`) to all export routes
|
||||
- [ ] 4.11 `[sonnet]` Add `response.ok` checks before `.json()` in `src/app/page.tsx` (lines 214, 230, 245, 257)
|
||||
- [ ] 4.12 `[sonnet]` Add `response.ok` checks before `.json()` in `src/components/CandleChart.tsx` (lines 163, 178, 192)
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,13 @@ import { db } from '@/lib/db';
|
|||
import { annotations, candles, charts } from '@/lib/db/schema';
|
||||
import { eq, and, desc } from 'drizzle-orm';
|
||||
|
||||
function sanitizeCsvCell(value: string): string {
|
||||
if (/^[=+@\-]/.test(value)) {
|
||||
return `'${value}`;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
export async function GET(request: NextRequest) {
|
||||
try {
|
||||
const { searchParams } = request.nextUrl;
|
||||
|
|
@ -57,7 +64,7 @@ export async function GET(request: NextRequest) {
|
|||
}
|
||||
|
||||
csvRows.push(
|
||||
`${annotation.timestamp},${annotation.label_type},${price !== null ? price : ''}`
|
||||
`${sanitizeCsvCell(String(annotation.timestamp))},${sanitizeCsvCell(annotation.label_type)},${price !== null ? price : ''}`
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,13 @@ import { db } from '@/lib/db';
|
|||
import { spanAnnotations, charts } from '@/lib/db/schema';
|
||||
import { eq, desc } from 'drizzle-orm';
|
||||
|
||||
function sanitizeCsvCell(value: string): string {
|
||||
if (/^[=+@\-]/.test(value)) {
|
||||
return `'${value}`;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
/**
|
||||
* GET /api/span-annotations/export
|
||||
*
|
||||
|
|
@ -97,9 +104,9 @@ export async function GET(request: NextRequest) {
|
|||
|
||||
for (const span of spans) {
|
||||
const notes = span.notes ? `"${span.notes.replace(/"/g, '""')}"` : '';
|
||||
|
||||
|
||||
csvRows.push(
|
||||
`${span.id},${span.start_time},${span.end_time},${span.label},${span.confidence || ''},${span.outcome || ''},${notes}`
|
||||
`${span.id},${sanitizeCsvCell(String(span.start_time))},${sanitizeCsvCell(String(span.end_time))},${sanitizeCsvCell(span.label)},${span.confidence || ''},${span.outcome ? sanitizeCsvCell(span.outcome) : ''},${notes}`
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue