Files
Kordant/tasks/security-fixes/10-fix-voiceprint-resource-exhaustion.md

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:

  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