4.9 KiB
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:
- Memory exhaustion: Upload extremely large audio files, causing the server to allocate massive intermediate buffers during audio preprocessing (Float64Array + Int16Array + Buffer = 8x+ input size)
- Disk exhaustion: Upload unlimited audio files with no per-user storage quota, filling the disk
- CPU exhaustion: Submit unlimited analysis/enrollment requests with no rate limiting, consuming CPU for audio preprocessing and Azure API calls
- Enrollment table bloat: Create unlimited voice enrollments with no per-user cap
Attack Vectors
POST /voiceprint/createEnrollment— No rate limit, no enrollment count capPOST /voiceprint/enrollAdditionalSample— No rate limitPOST /voiceprint/analyzeAudio— No rate limit on memory-intensive analysisPOST /voiceprint/analyzeCallRecording— No rate limitsaveAudio()— No deduplication, no storage quota checkpreprocessAudio()— 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 checkenrollAdditionalSample— Rate limited before enrollment lookupanalyzeAudio— Rate limited after access check but before processinganalyzeCallRecording— 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_BYTESenv var (default: 50MB) - Added
getUserStorageUsage(userId)to calculate total disk usage - Added
checkStorageQuota(userId, fileSizeBytes)to enforce quota before writes saveAudionow 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: falseif file exists (skips write) - Returns
isNew: trueif 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 statsweb/src/server/services/voiceprint/storage.ts— Storage quota, deduplication, usage trackingweb/src/server/services/voiceprint/audio.processor.ts— Input size limit, duration validationweb/src/server/services/voiceprint.service.test.ts— Tests for all new protectionsweb/src/server/services/voiceprint/storage.test.ts— Tests for quota and deduplicationweb/src/server/services/voiceprint/audio.processor.test.ts— Tests for size/duration validation
Defense in Depth
The fix implements multiple layers of protection:
- Schema-level (valibot):
MAX_BASE64_LENGTHrejects oversized base64 at request parsing - Service-level:
validateDecodedSizechecks decoded buffer size before allocation - Processor-level:
MAX_INPUT_BYTESrejects large WAV files, header duration check rejects long audio - Storage-level: Quota check before disk write, deduplication prevents redundant storage
- Rate limiting: Redis-based sliding window prevents rapid-fire requests
- Enrollment cap: Prevents unlimited enrollment creation