feat(voice): GPU-less client-side speaker-embedding routing (audit H3, flag-gated default OFF)#225
Merged
Conversation
…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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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/enrollpath is unchanged.What's added
ClientSideVoiceEmbeddingPolicy(app.auth.client-side-voice-embedding) — VOICE twin ofClientSideEmbeddingPolicy, a separate class so voice + face are independent kill-switches. Global master + per-tenant canary, identical parse/overload semantics.verifyVoiceEmbedding/enrollVoiceEmbeddingPOST JSON{user_id, embedding[256], tenant_id?, optimize?}to the bio voice embedding routes via the shared X-API-KeyRestClient, 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 MFAdatacarries a precomputedembedding, match via/voice/verify-embedding; otherwise the UNCHANGED legacyvoiceDatapath.entity.UserArchUnit boundary respected (cachesuser.getId()once, no newentity.Useraccess inapplication..).BiometricController.enrollVoiceEmbedding—POST /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.APP_AUTH_CLIENT_SIDE_VOICE_EMBEDDING(_TENANTS)in the prodenvironment:block (explicit block, NOTenv_file:— a var only in.env.prodis dropped; this exact gap once broke the face flag) +application.yml/application-prod.ymldefaults (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-processordocs/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, + theenrollVoiceEmbedding@PreAuthorizesweep, + the@WebMvcTestcontroller 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).