security: add file size (10MB) and row count (500k) limits to upload route
- Reject uploads larger than 10MB before reading file content - Reject CSVs with more than 500,000 data rows after parsing - Checks placed as early as possible in the handler flow - Mark task 2.3 as done in tasks.md Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
67dd7aa2f0
commit
0e239dc3da
2 changed files with 21 additions and 1 deletions
|
|
@ -13,7 +13,7 @@
|
||||||
|
|
||||||
- [x] 2.1 `[haiku]` Validate `run_id` matches `/^[a-zA-Z0-9_-]+$/` in `src/app/api/training/runs/[run_id]/route.ts` before interpolation
|
- [x] 2.1 `[haiku]` Validate `run_id` matches `/^[a-zA-Z0-9_-]+$/` in `src/app/api/training/runs/[run_id]/route.ts` before interpolation
|
||||||
- [x] 2.2 `[sonnet]` Validate `run_id` format and use `Path.resolve()` + directory containment check in `services/ml/app/main.py` (model load at line 1203, delete at line 1312)
|
- [x] 2.2 `[sonnet]` Validate `run_id` format and use `Path.resolve()` + directory containment check in `services/ml/app/main.py` (model load at line 1203, delete at line 1312)
|
||||||
- [ ] 2.3 `[sonnet]` Add file size check (reject >10MB) and row count limit (500,000) to `src/app/api/upload/route.ts`
|
- [x] 2.3 `[sonnet]` Add file size check (reject >10MB) and row count limit (500,000) to `src/app/api/upload/route.ts`
|
||||||
- [ ] 2.4 `[haiku]` Add file type validation (`.csv` extension, text MIME type) to `src/app/api/upload/route.ts`
|
- [ ] 2.4 `[haiku]` Add file type validation (`.csv` extension, text MIME type) to `src/app/api/upload/route.ts`
|
||||||
- [ ] 2.5 `[haiku]` Fix CORS in `services/ml/app/main.py`: replace `allow_origins=["*"]` with `["http://localhost:3000"]` and support `CORS_ORIGINS` env var
|
- [ ] 2.5 `[haiku]` Fix CORS in `services/ml/app/main.py`: replace `allow_origins=["*"]` with `["http://localhost:3000"]` and support `CORS_ORIGINS` env var
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,14 @@ export async function POST(request: NextRequest): Promise<NextResponse> {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const MAX_FILE_SIZE = 10 * 1024 * 1024; // 10MB
|
||||||
|
if (file.size > MAX_FILE_SIZE) {
|
||||||
|
return NextResponse.json(
|
||||||
|
{ error: 'File too large. Maximum size is 10MB.' },
|
||||||
|
{ status: 400 }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
const text = await file.text();
|
const text = await file.text();
|
||||||
|
|
||||||
// Derive chart name from filename (strip .csv extension)
|
// Derive chart name from filename (strip .csv extension)
|
||||||
|
|
@ -50,6 +58,18 @@ export async function POST(request: NextRequest): Promise<NextResponse> {
|
||||||
try {
|
try {
|
||||||
const rows = results.data as any[];
|
const rows = results.data as any[];
|
||||||
|
|
||||||
|
// Validate row count
|
||||||
|
const MAX_ROWS = 500000;
|
||||||
|
if (rows.length > MAX_ROWS) {
|
||||||
|
resolve(
|
||||||
|
NextResponse.json(
|
||||||
|
{ error: 'File contains too many rows. Maximum is 500,000.' },
|
||||||
|
{ status: 400 }
|
||||||
|
)
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// Validate headers
|
// Validate headers
|
||||||
if (rows.length === 0) {
|
if (rows.length === 0) {
|
||||||
resolve(
|
resolve(
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue