Inspiration 🌭

Every bank call assumes the voice on the other end is human. Voice cloning breaks that assumption. With only 353 labeled calls, our first question wasn't "what model?" — it was "how do we build something robust without just memorizing 353 examples?" That became our whole philosophy: build, stress-test, understand why it fails, improve, repeat.

What it does 🌭

CHORI listens to the caller side of an 8kHz stereo call and returns:

{"is_synthetic": true, "confidence": 0.87}

Two acoustic perspectives, fused: spectral (LFCC — what survives an 8kHz phone line) and prosodic (F0 via YIN, jitter/shimmer — how the voice physically behaves), calibrated with Platt Scaling. Served via POST /detect, live on Vultr, CPU-only.

How we built it 🌭

  • 🌭 Audited the 353 calls before training anything — found a single bandwidth feature separating classes at AUC 0.82. A model can be right for the wrong reason.
  • 🌭 Built two independent acoustic branches instead of one bigger one — spectral and prosodic ask different questions.
  • 🌭 Stress-tested first (noise, pitch shift, time stretch, low-pass, Opus), augmented second — never the other way around.
  • 🌭 Ran confound controls: fed the model silence only (AUC 0.9994) and the agent's channel only, same TTS both classes (AUC 0.7397). Both should've failed. Neither did — now documented, not hidden.
  • 🌭 Shipped it: POST /detect, 182ms end-to-end on CPU, deployed on Vultr.

Challenges we ran into 🌭

Dataset shortcuts were the real boss fight, not model accuracy — bandwidth, codec, silence can all become accidental labels with only 353 calls. We also built (and didn't ship) a fused prosodic candidate, C3: it looked better, but on data that had already touched its own evaluation. We shipped the model we could defend, not the one with the highest number.

Accomplishments that we're proud of 🌭

A working prototype, not a slide. An audit trail that catches our own model cheating — twice. 182ms inference on CPU, zero GPU. 360 real calls against the deployed endpoint, zero errors. And the discipline to not ship the model we couldn't yet trust.

What we learned 🌭

Accuracy isn't the same as trustworthiness. A perfect AUC is a question, not an answer. Stress test before you augment. And a banking detector needs a cost-aware threshold, not just AUC — a false alarm on a real customer isn't free.

What's next 🌭

Cross-vendor generalization: 353 calls and one TTS vendor isn't the whole attack surface. Next: an ongoing red-team loop with new voices (ElevenLabs and others), continuous recalibration, and validating improvements like C3 with a truly independent holdout before they ever touch production.

CHORI — Evidence that survives. 🌭

Built With

Share this project:

Updates