100 lines
4.9 KiB
Markdown
100 lines
4.9 KiB
Markdown
# Task 10: Fix VoicePrint Resource Exhaustion via Unbounded Audio Upload
|
|
|
|
## Vulnerability
|
|
|
|
The VoicePrint module was vulnerable to resource exhaustion attacks through unbounded audio uploads. An attacker with a valid account could:
|
|
|
|
1. **Memory exhaustion**: Upload extremely large audio files, causing the server to allocate massive intermediate buffers during audio preprocessing (Float64Array + Int16Array + Buffer = 8x+ input size)
|
|
2. **Disk exhaustion**: Upload unlimited audio files with no per-user storage quota, filling the disk
|
|
3. **CPU exhaustion**: Submit unlimited analysis/enrollment requests with no rate limiting, consuming CPU for audio preprocessing and Azure API calls
|
|
4. **Enrollment table bloat**: Create unlimited voice enrollments with no per-user cap
|
|
|
|
## Attack Vectors
|
|
|
|
- `POST /voiceprint/createEnrollment` — No rate limit, no enrollment count cap
|
|
- `POST /voiceprint/enrollAdditionalSample` — No rate limit
|
|
- `POST /voiceprint/analyzeAudio` — No rate limit on memory-intensive analysis
|
|
- `POST /voiceprint/analyzeCallRecording` — No rate limit
|
|
- `saveAudio()` — No deduplication, no storage quota check
|
|
- `preprocessAudio()` — No input size limit, no duration validation from header
|
|
|
|
## Fixes Implemented
|
|
|
|
### 1. Rate Limiting (`voiceprint.service.ts`)
|
|
|
|
Added rate limiting using the existing `memory` tier (10 requests/hour per user) to all audio upload endpoints:
|
|
- `createEnrollment` — Rate limited before enrollment count check
|
|
- `enrollAdditionalSample` — Rate limited before enrollment lookup
|
|
- `analyzeAudio` — Rate limited after access check but before processing
|
|
- `analyzeCallRecording` — Rate limited after access check
|
|
|
|
### 2. Per-User Enrollment Limit (`voiceprint.service.ts`)
|
|
|
|
Added `VOICEPRINT_MAX_ENROLLMENTS` env var (default: 5) to cap active enrollments per user. `createEnrollment` now checks the count before proceeding.
|
|
|
|
### 3. Per-User Storage Quota (`storage.ts`)
|
|
|
|
- Added `VOICEPRINT_MAX_USER_STORAGE_BYTES` env var (default: 50MB)
|
|
- Added `getUserStorageUsage(userId)` to calculate total disk usage
|
|
- Added `checkStorageQuota(userId, fileSizeBytes)` to enforce quota before writes
|
|
- `saveAudio` now checks quota before writing new files
|
|
|
|
### 4. Audio Deduplication (`storage.ts`)
|
|
|
|
`saveAudio` now:
|
|
- Computes SHA-256 hash of input
|
|
- Checks if file with same hash already exists
|
|
- Returns `isNew: false` if file exists (skips write)
|
|
- Returns `isNew: true` if file is new
|
|
|
|
This prevents redundant storage and CPU waste from duplicate uploads.
|
|
|
|
### 5. Input Size Validation (`audio.processor.ts`)
|
|
|
|
Added `VOICEPRINT_MAX_INPUT_BYTES` env var (default: 5MB) to reject oversized WAV files before any processing. Error message includes suggested action.
|
|
|
|
### 6. Duration Validation from Header (`audio.processor.ts`)
|
|
|
|
Before allocating sample buffers, the processor now:
|
|
- Parses WAV header to extract duration
|
|
- Rejects files exceeding `MAX_DURATION_SEC + 30` (60s total)
|
|
- This prevents loading multi-hour WAV files into memory
|
|
|
|
### 7. Storage Usage in Stats (`voiceprint.service.ts`)
|
|
|
|
`getUsageStats` now returns `storageUsedBytes` and `storageUsedMB` so users can monitor their disk usage.
|
|
|
|
## Configuration
|
|
|
|
All limits are configurable via environment variables:
|
|
|
|
| Variable | Default | Description |
|
|
|----------|---------|-------------|
|
|
| `VOICEPRINT_MAX_BASE64_LENGTH` | 2621440 (~2.6MB) | Max base64 string length (schema-level) |
|
|
| `VOICEPRINT_MAX_DECODED_SIZE` | 2097152 (2MB) | Max decoded buffer size |
|
|
| `VOICEPRINT_MAX_INPUT_BYTES` | 5242880 (5MB) | Max raw WAV file size |
|
|
| `VOICEPRINT_MAX_USER_STORAGE_BYTES` | 52428800 (50MB) | Per-user disk storage quota |
|
|
| `VOICEPRINT_MAX_ENROLLMENTS` | 5 | Max active enrollments per user |
|
|
|
|
Rate limiting uses the existing `memory` tier: 10 requests per hour per user (via Redis sliding window).
|
|
|
|
## Files Modified
|
|
|
|
- `web/src/server/services/voiceprint.service.ts` — Rate limiting, enrollment limits, storage stats
|
|
- `web/src/server/services/voiceprint/storage.ts` — Storage quota, deduplication, usage tracking
|
|
- `web/src/server/services/voiceprint/audio.processor.ts` — Input size limit, duration validation
|
|
- `web/src/server/services/voiceprint.service.test.ts` — Tests for all new protections
|
|
- `web/src/server/services/voiceprint/storage.test.ts` — Tests for quota and deduplication
|
|
- `web/src/server/services/voiceprint/audio.processor.test.ts` — Tests for size/duration validation
|
|
|
|
## Defense in Depth
|
|
|
|
The fix implements multiple layers of protection:
|
|
|
|
1. **Schema-level** (valibot): `MAX_BASE64_LENGTH` rejects oversized base64 at request parsing
|
|
2. **Service-level**: `validateDecodedSize` checks decoded buffer size before allocation
|
|
3. **Processor-level**: `MAX_INPUT_BYTES` rejects large WAV files, header duration check rejects long audio
|
|
4. **Storage-level**: Quota check before disk write, deduplication prevents redundant storage
|
|
5. **Rate limiting**: Redis-based sliding window prevents rapid-fire requests
|
|
6. **Enrollment cap**: Prevents unlimited enrollment creation
|