Skip to content

Security: Usergy-ops/voice-platform

Security

docs/SECURITY.md

Security

Authentication

Dual-Token JWT System

Token Lifetime Storage Purpose
Access Token 15 minutes JavaScript memory (Authorization header) API request authentication
Refresh Token 30 days HttpOnly cookie (SameSite=Lax, Secure) Silent access token renewal

Token rotation: Every refresh request invalidates the old refresh token and issues a new one. If a stolen refresh token is reused after the legitimate user has rotated, the stolen token is already revoked.

Logout: Revokes current refresh token. logout-all revokes every refresh token for the user.

Google OAuth

Google ID tokens are verified server-side using google-auth-library. On first sign-in, a new User record is created. Subsequent sign-ins match by email.

OTP Security

  • 6-digit codes, bcrypt-hashed before storage
  • 10-minute expiry (configurable via OTP_EXPIRY_SECONDS)
  • Max 5 verification attempts per OTP
  • Generic error messages (don't reveal whether email exists)
  • Rate limited: 3 sends per 5 minutes, 5 verifies per 5 minutes

CSRF Protection

Middleware: csrf.ts

Validates Origin or Referer header on all state-mutating requests (POST, PUT, PATCH, DELETE). Allowed origins are derived from FRONTEND_URL env var.

  • Requests with no Origin AND no Referer are allowed through (server-to-server / non-browser clients)
  • Only browser requests (which include Origin headers) are checked
  • GET, HEAD, OPTIONS are exempt

Rate Limiting

In-memory store via express-rate-limit. Per-IP tracking.

Endpoint Limit Window Response
POST /auth/send-magic-link 3 requests 5 minutes 429 + Retry-After
POST /auth/verify-email-otp 5 requests 5 minutes 429 + Retry-After
POST /recordings/chunk 30 requests 1 minute 429 + Retry-After
All other endpoints 100 requests 1 minute 429 + Retry-After

Note: In-memory store resets on server restart. For multi-instance deployments, swap to Redis-backed store.


HTTP Security Headers

Next.js (next.config.ts) sets these headers on all frontend responses:

Header Value
Strict-Transport-Security max-age=63072000; includeSubDomains; preload
X-Frame-Options DENY
X-Content-Type-Options nosniff
X-DNS-Prefetch-Control on
Referrer-Policy strict-origin-when-cross-origin
Permissions-Policy microphone=(self), camera=(), geolocation=(), payment=()
Content-Security-Policy default-src 'self'; connect-src 'self' <API_URL> wss://livekit...; ...

Express uses helmet middleware for API-side security headers.


Azure Blob Storage Security

SAS Token Scoping

  • Upload SAS tokens: Write-only permission (w), valid for 30 minutes, scoped to specific blob path
  • Download SAS tokens: Read-only permission (r), valid for 60 minutes, generated on-demand by admin
  • Permission type is validated as a string enum (not arbitrary values)

Blob Naming Convention

voice-recordings/
  sessions/{sessionId}/{recordingId}/
    chunk-{index}.wav        # Individual 10s chunks
    final.wav                # Merged 24-bit WAV

Input Validation

Zod schemas on every route that accepts input:

Route Validated Fields
Auth: send-magic-link email (valid email format)
Auth: verify-otp email, otp (6 chars)
Auth: google idToken (non-empty string)
Recordings: chunk sessionId (UUID), recordingId (1-200 chars), chunkIndex (0-10000)
Recordings: finalize sessionId (UUID), recordingId (1-200 chars), totalChunks (optional)
Profile: update name (2-100 chars), preferredLanguage (max 10 chars)
Profile: payment accountNumber (9-18 digits), ifscCode (regex), panNumber (regex)
Admin: pagination page (min 1), limit (1-100), search (max 100 chars)
Admin: reject reason (1-500 chars)
Admin: bulk approve ids (1-100 UUIDs)
Consent consents array (1-10 items, enum type)

Audio Chunk Validation

Binary format validation on upload:

  • WAV: RIFF header at offset 0 + WAVE header at offset 8
  • WebM: EBML header (0x1A 0x45 0xDF 0xA3) at offset 0
  • OGG: "OggS" magic bytes at offset 0
  • Minimum chunk size: 1KB (rejects empty/corrupt files)
  • Maximum chunk size: 60MB (multer limit)

Logging & Data Protection

Sensitive Data Redaction

The Winston logger automatically redacts fields matching these patterns from all log output:

otp, password, token, accessToken, refreshToken, secret, accountNumber, ifscCode, upiId

All logged as [REDACTED].

Payment Data Masking

API responses mask sensitive payment fields:

  • Account number: ****1234 (last 4 digits only)
  • PAN number: ******1234 (last 4 only)

Raw values are never sent to the client.

Request Correlation

Every request gets a unique X-Request-ID (UUID). This ID is:

  • Set on the response header for client-side tracing
  • Automatically injected into every log entry via AsyncLocalStorage
  • Included in error responses for support debugging

Session Security

  • Sessions use UUIDs as IDs (not sequential integers)
  • Invite codes are 8-character alphanumeric (nanoid)
  • Users can only access sessions they are host or guest of
  • Only the host can start/end recording
  • Session state transitions are validated (prevents skip-ahead attacks)

There aren't any published security advisories