Skip to content

feat(voice): GPU-less client-side speaker-embedding routing (audit H3, flag-gated default OFF)#225

Merged
ahmetabdullahgultekin merged 1 commit into
mainfrom
feat/voice-client-embedding
Jun 12, 2026
Merged

feat(voice): GPU-less client-side speaker-embedding routing (audit H3, flag-gated default OFF)#225
ahmetabdullahgultekin merged 1 commit into
mainfrom
feat/voice-client-embedding

Conversation

@ahmetabdullahgultekin

Copy link
Copy Markdown
Contributor

Summary

Audit item H3 (identity-core-api side). Route VOICE verify + enroll to the new biometric-processor client-side-embedding voice endpoints: accept a browser-computed 256-d Resemblyzer speaker embedding and forward it to /voice/verify-embedding + /voice/enroll-embedding, so the raw audio never leaves the device and the CPU-only bio box skips the Resemblyzer forward pass. Mirrors the existing FACE client-embedding wiring.

Default OFF, reversible via env flag (no redeploy). Legacy audio /voice/verify + /voice/enroll path is unchanged.

What's added

  • ClientSideVoiceEmbeddingPolicy (app.auth.client-side-voice-embedding) — VOICE twin of ClientSideEmbeddingPolicy, a separate class so voice + face are independent kill-switches. Global master + per-tenant canary, identical parse/overload semantics.
  • Port + adapter: verifyVoiceEmbedding / enrollVoiceEmbedding POST JSON {user_id, embedding[256], tenant_id?, optimize?} to the bio voice embedding routes via the shared X-API-Key RestClient, with the same fail-closed error mapping as the face embedding methods.
  • VoiceVerifyMfaStepHandler: when the voice policy is ON for the session tenant AND the MFA data carries a precomputed embedding, match via /voice/verify-embedding; otherwise the UNCHANGED legacy voiceData path. entity.User ArchUnit boundary respected (caches user.getId() once, no new entity.User access in application..).
  • BiometricController.enrollVoiceEmbeddingPOST /api/v1/biometric/voice/enroll-embedding/{userId} + VoiceEnrollEmbeddingRequest (256-length-validated → 400), fail-closed when the policy is OFF, same @PreAuthorize + per-user enrollment cap as the audio enroll.
  • Compose passthrough APP_AUTH_CLIENT_SIDE_VOICE_EMBEDDING(_TENANTS) in the prod environment: block (explicit block, NOT env_file: — a var only in .env.prod is dropped; this exact gap once broke the face flag) + application.yml / application-prod.yml defaults (false/empty).

Security + rollout

An embedding carries no audio ⇒ the bio side cannot run its replay/liveness check on it — an embedding VOICE factor MUST be paired with a liveness factor in the auth flow. Ordering: flip this (identity) flag ON BEFORE the web VITE_CLIENT_SIDE_VOICE_EMBEDDING. The web preprocessing port is still a documented scaffold (browser mel+VAD not yet parity-validated — see biometric-processor docs/design/VOICE_CLIENT_EMBEDDING_SPEC.md); keep the voice flag OFF until it is.

Tests

ClientSideVoiceEmbeddingPolicyTest, VoiceVerifyMfaStepHandlerTest (audio↔embedding routing across policy states), +5 adapter HTTP cases, + the enrollVoiceEmbedding @PreAuthorize sweep, + the @WebMvcTest controller test's new @MockBean. Full suite green: mvn -o test = 1840 run / 0 fail / 0 error / 67 skipped (Testcontainers ITs), JDK 21. UserDomainBoundaryTest (ArchUnit) green.

Paired PRs: biometric-processor (endpoints + ONNX export, VERIFIED) + web-app (client module, default OFF).

…ndpoints (audit H3, flag-gated default OFF)

GPU-less VOICE: accept a browser-computed 256-d Resemblyzer speaker embedding and
route it to the new bio /voice/verify-embedding + /voice/enroll-embedding
endpoints, so the raw audio never leaves the device and the CPU-only server skips
the Resemblyzer forward pass. Mirrors the existing FACE client-embedding wiring.
Default OFF (legacy audio path unchanged); reversible via env flag, no redeploy.

- ClientSideVoiceEmbeddingPolicy (app.auth.client-side-voice-embedding) — VOICE
  twin of ClientSideEmbeddingPolicy; SEPARATE class so voice/face are independent
  kill-switches. Global + per-tenant-canary, same parse/overload semantics.
- BiometricServicePort.verifyVoiceEmbedding / enrollVoiceEmbedding + adapter impls
  POSTing JSON {user_id, embedding[256], tenant_id?, optimize?} to the bio voice
  embedding routes, with the same fail-closed error mapping as the face methods.
- VoiceVerifyMfaStepHandler: when the voice policy is ON for the tenant AND the
  MFA data carries a precomputed embedding, match via /voice/verify-embedding;
  else the UNCHANGED legacy voiceData path. entity.User boundary respected
  (cache user.getId() once, no new entity.User access in application/controller).
- BiometricController.enrollVoiceEmbedding — POST /api/v1/biometric/voice/
  enroll-embedding/{userId} + VoiceEnrollEmbeddingRequest (256-length-validated),
  fail-closed when policy OFF, same @PreAuthorize + per-user cap as audio enroll.
- Compose passthrough APP_AUTH_CLIENT_SIDE_VOICE_EMBEDDING(_TENANTS) in the prod
  environment: block (explicit block, NOT env_file:) + application(.yml/-prod.yml)
  defaults (false/empty).

SECURITY: embedding carries no audio ⇒ no server replay/liveness check; an
embedding VOICE factor MUST be paired with a liveness factor. Rollout: flip this
flag ON before the web flag. The web preprocessing port is a documented scaffold
(see biometric-processor VOICE_CLIENT_EMBEDDING_SPEC.md) — keep OFF until validated.

Tests: ClientSideVoiceEmbeddingPolicyTest, VoiceVerifyMfaStepHandlerTest, +5
adapter HTTP cases, + voice enroll @PreAuthorize sweep, + the WebMvc controller
test's new @MockBean. Full suite green: mvn -o test = 1840 run / 0 fail / 0 error
/ 67 skipped (Testcontainers ITs), JDK 21. ArchUnit UserDomainBoundaryTest green.
@ahmetabdullahgultekin ahmetabdullahgultekin merged commit 9e61d3e into main Jun 12, 2026
4 checks passed
@ahmetabdullahgultekin ahmetabdullahgultekin deleted the feat/voice-client-embedding branch June 12, 2026 20:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant