Skip to main content

VoiceSafeKit

CI Zero dependencies Python 3.10-3.13 MIT

Try it in your browser · Benchmark · Pipecat · LiveKit · Open WebUI · LiteLLM · Audio · Changelog

VoiceSafeKit

Your voice agent is sending card numbers to the LLM, and your PII filter can't see them.

A spoken card number and email are recorded in the browser, transcribed by Whisper on-device, and both are flagged for redaction
A voice says a card number and an email into the live app. Whisper runs on-device and both are caught. Recording sped up 5×.

Speech-to-text writes what people say, not what they mean:

my card is four two four two four two four two four two four two four two four two
email me at alex dot rivera at gmail dot com
call me on oh seven seven double oh nine double oh one two three

Regex filters and NER models (Presidio, most guardrail libraries) are built for written text, so they pass all three straight through. VoiceSafeKit reads speech the way STT writes it, redacts the sensitive parts, and gives the values back when the agent needs to say them. It can also bleep them out of the call recording. Zero dependencies, about 0.06 ms per utterance, and drop-ins for Pipecat, LiveKit Agents, Open WebUI and LiteLLM.

User says    : Hi, my name is Alex Rivera. My card number is four two four two four two four two
               four two four two four two four two and my email is alex dot rivera at gmail dot com.
LLM receives : Hi, my name is [NAME_1]. My card number is [CARD_1] and my email is [EMAIL_1].
TTS says     : Thanks Alex Rivera. I'll send the receipt to alex dot rivera at gmail dot com.

Output of examples/pipecat/offline_demo.py, which runs without API keys.

Install

pip install voicesafekit
# or, before the first PyPI release: pip install git+https://github.com/MohamadKanso/VoiceSafeKit
# extras: [pipecat] [livekit] [openwebui] [litellm] [audio] [crypto]
echo "my pin is four eight two one" | voicesafekit redact
# my pin is [PIN removed]

See it

Spoken PII caught in the live app, with no digits or @ signs anywhere in the transcript:

Capture and Check panels: a spoken card number, email and one-time code are each flagged and marked caught in spoken form

Inspect tab highlighting the spoken name, card number, email and code in the original transcript Protect panel with the safe transcript and guidance for the assistant
Inspect: the exact spoken words that were flagged. Protect: the safe transcript and guidance for the assistant.

Table comparing a text-only filter, which marks spoken PII as SAFE, with VoiceSafeKit, which redacts it

SpokenPII benchmark

No public benchmark measures PII as speech-to-text writes it, so this repo ships one.

Held-out real speech (the honest number). New scripts read aloud by a British TTS voice (Daniel), transcribed by faster-whisper small.en, frozen before any 0.6 change and never used for tuning. A sample counts as protected only if the value can't be recovered from the redacted transcript.

Detector PII protected (not leaked) False alarms on no-PII
VoiceSafeKit 0.6 98% (106/108) 2% (1/40)
VoiceSafeKit 0.5 94% (102/108) 2% (1/40)
VoiceSafeKit 0.6, spoken mode off 87% (94/108) 2% (1/40)
Microsoft Presidio 2.2 65% (70/108) 42% (17/40)

The three failures are published in #9: a PIN Whisper wrote as money ($4,990), a comma before the birth year, and a mangled date read as a phone number.

First real-speech set (Samantha voice, Whisper base.en)

v0.5 protected 101/108 on this set. Its 7 misses (comma-split digit groups, SSN609507896, digits written as decimals, "fin" for PIN) were fixed in 0.6 (#6), so 0.6 now scores 108/108 here. Because the fixes were written against these misses, this set is no longer held out. Presidio: 79/108, 17/40 false alarms.

SpokenPII (synthetic). 540 labelled sentences, the same values written two ways, plus 40 tricky no-PII requests ("emergency fund", "call Sue", "twenty five minutes"). A span counts as protected only if at least 90% of it is masked.

Detector Written PII protected Spoken PII protected False alarms on no-PII Median latency
VoiceSafeKit 0.6 100% 100% 0% 0.06 ms
VoiceSafeKit 0.6, spoken mode off 100% 11% 0% 0.04 ms
Microsoft Presidio 2.2 (spaCy en_core_web_lg) 86% 21% 45% ~3 ms

Spoken PII protected, by type:

Type VoiceSafeKit Presidio
Card number 100% 0%
SSN 100% 3%
Phone 100% 0%
Email 100% 0%
PIN 100% 13%
One-time code 100% 3%
Passport 100% 0%
Date of birth 100% 77%
Name 100% 97%

Read these numbers honestly. I wrote the synthetic set and the detector, so 100% there mostly shows the templates are covered; the held-out real-speech set is the fairer test. Presidio is a general-purpose tool configured with its defaults, not tuned for speech. Its false alarms are mostly DATE_TIME and PERSON hits on ordinary requests. Reproduce everything with python benchmarks/run.py --presidio and python benchmarks/asr.py score --presidio --data benchmarks/data/spokenpii-asr-heldout.jsonl. Better still, add transcripts from your own STT engine.

What it catches

Category Detectors
Spoken forms digits as words, "oh" / "double" / "triple", "plus four four", tens and hundreds, spoken emails, spoken dates
Payment card number (Luhn + context), partial card ("ends in 4821"), CVV, PIN, IBAN (mod-97)
Identity SSN (with or without dashes), passport, UK National Insurance, date of birth, self-introduced name
Contact email, phone, street address, UK postcode, IP address
Credentials passwords and secrets in speech, one-time / verification codes, API keys (OpenAI, AWS, GitHub, Slack, Google)
Safety signals emergency, emotional distress, coercion, medical / legal / financial advice requests

Every result has a decision (SAFE, REDACT, REVIEW, BLOCK), a 0-100 score, per-finding confidence and spoken flags, a safe_transcript, a redaction_map and plain-English assistant_guidance.

Python API

from voicesafekit import analyze_transcript

result = analyze_transcript("my social is four seven one, five five, eight eight four three")
result.decision            # "BLOCK"
result.safe_transcript     # "my social is [SSN removed]"
result.findings[0].spoken  # True

Reversible placeholders

Plain redaction breaks agents that need to say "I've sent it to your email". A PrivacySession swaps values for placeholders, keeps the map in memory, and restores them in the reply, including streamed tokens:

from voicesafekit import PrivacySession

session = PrivacySession()
safe = session.protect("email me at alex dot rivera at gmail dot com")
safe.text                       # "email me at [EMAIL_1]"
reply = llm(safe.text)          # "Done, I've emailed [EMAIL_1]."
session.restore(reply)          # "Done, I've emailed alex dot rivera at gmail dot com."

stream = session.stream()       # for token streams: "[EMA" + "IL_1]" restores correctly

The same value gets the same placeholder for the whole conversation, whether it was spoken or written. Passwords, PINs, CVVs, one-time codes and API keys are never stored, so the model can't make the voice read them back.

Pipecat

from voicesafekit.integrations.pipecat import voicesafe_processors

guard_in, restore_out, guard_context = voicesafe_processors()

pipeline = Pipeline([
    transport.input(), stt,
    guard_in,             # spoken PII -> [CARD_1], [EMAIL_1] before the LLM
    user_aggregator, llm,
    restore_out,          # real values back for the voice only
    tts, transport.output(),
    guard_context,        # stored assistant replies keep the placeholders
    assistant_aggregator,
])

@guard_in.event_handler("on_block")
async def on_block(processor, protected):
    ...  # emergencies, government IDs, CVVs: never forwarded, refusal is spoken

Many STT services send several final transcriptions per turn. When one ends mid-number near a sensitive word ("my card number is four two four two"), the processor holds it for up to 0.8 s and merges it with the next, so a card number split across frames is still caught.

LiveKit Agents

from voicesafekit.integrations.livekit import VoiceSafeAgent

class Assistant(VoiceSafeAgent):          # instead of Agent
    def __init__(self):
        super().__init__(instructions="You are a helpful voice assistant.")

VoiceSafeAgent protects each user turn in on_user_turn_completed, restores values in tts_node, and stops blocked turns. VoiceSafeRedactor().redact(text) is a stateless, idempotent redactor for transcript and log hooks. Full example: examples/livekit/agent.py.

Open WebUI

A Filter Function: Admin Panel > Functions > + > paste examples/openwebui/voicesafekit_filter.py > Save, then enable it. It installs VoiceSafeKit through its requirements: line.

  • Every user message is protected before the model sees it ("my card is four two four two ..." becomes "my card is [CARD_1]"), and real values come back in the reply on the user's own screen, streamed or not.
  • Placeholders stay consistent per chat and per user. A new message carrying a government ID, card security code or emergency is stopped with a clear message (valve block_on_critical).
  • Valves: spoken, block_on_critical, restore_in_output, max_chats, priority.

Open WebUI's background tasks (title, tags, follow-ups, autocomplete) call the task model directly and skip filters, so turn them off or use a local task model for full privacy. Built against Open WebUI 0.13 and tested with the request shapes from its source.

LiteLLM

A custom guardrail for the LiteLLM proxy, so every app behind it gets the same protection:

guardrails:
  - guardrail_name: voicesafekit
    litellm_params:
      guardrail: voicesafekit.integrations.litellm.VoiceSafeGuardrail
      mode: [pre_call, post_call]   # redact before the model, restore after (streaming too)
      default_on: true
      block_on_critical: true       # HTTP 400 for government IDs, CVVs, emergencies
pip install "voicesafekit[litellm]"
litellm --config config.yaml

Tested on a running LiteLLM 1.108 proxy: the upstream model received my card is [CARD_1], the client got the real values back (normal and streamed), and an SSN request returned HTTP 400 without reaching the model. See examples/litellm/.

Bleep the recording, not just the transcript

Redacting the transcript still leaves the card number in the call recording. VoiceSafeKit bleeps it out of the audio too, locally and for free, with standard-library code once you have word timestamps.

pip install "voicesafekit[audio]"           # adds faster-whisper for word timestamps
voicesafekit bleep call.wav -o call.bleeped.wav
# Hi, my name is [name removed], my card number is [payment number removed] and my pin is [PIN removed].
#
# Bleeped 3 span(s) -> call.bleeped.wav
#   00:00.950 - 00:01.870  Person name
#   00:02.990 - 00:07.330  Payment card-like number
#   00:07.730 - 00:08.850  PIN

Already have word timings from Deepgram, Whisper or WhisperX? Pass them and no extras are needed:

from voicesafekit.audio import bleep_wav, redact_audio_spans, words_from_deepgram

redaction = redact_audio_spans(words_from_deepgram(response))   # PII -> time spans
bleep_wav("call.wav", "call.bleeped.wav", redaction)            # or mode="silence"

Checked end to end: transcribing the bleeped file with Whisper finds no card or PIN digits, while the rest of the sentence still comes through. PCM WAV (8/16/24/32-bit, any channel count), streamed in chunks; every sample outside the spans is copied byte for byte.

OpenVoiceOS

from voicesafekit.integrations.openvoiceos import protect_utterance

result = protect_utterance(utterance)
if result.should_continue:
    send_to_llm(result.safe_transcript)

CLI

voicesafekit check examples/transcripts/identity_theft.txt --pretty
voicesafekit redact --placeholders < transcript.txt
voicesafekit conversation turn1.txt turn2.txt --pretty
voicesafekit bleep call.wav -o call.bleeped.wav            # needs [audio] or --words
voicesafekit check transcript.txt --out result.enc.json   # AES-256-GCM, needs [crypto]

Browser app

The live app records your voice, transcribes it with Whisper on your device and runs the same rules in the browser. No server and no API key, and nothing leaves the page. Say a card number out loud and watch it disappear.

python3 -m http.server 8124 --directory docs   # run it locally

Design principles

  • Speech first. Rules run on what STT actually produces, not on tidy written text.
  • Local and tiny. Pure Python, no dependencies, no network, no model download.
  • Auditable. Every finding has a confidence score and maps to exact spans.
  • Fail toward privacy. Credentials are never stored, blocked turns never reach the model.
  • Honest scope. It is a fast first layer, not a full privacy programme or a compliance certification. Pair it with your own review for PCI DSS, HIPAA or GDPR work.

Contributing

The most valuable contribution is a transcript your STT engine produced that VoiceSafeKit missed (fake data only). Use the Missed spoken PII template. See CONTRIBUTING.md and the roadmap.

pip install -e ".[dev]" && pytest && python benchmarks/run.py

License

MIT. See LICENSE. Not legal, medical or security advice.

Metadata

Release files for voicesafekit 0.6.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for voicesafekit 0.6.0
File Size Uploaded Provenance Actions
voicesafekit-0.6.0.tar.gz 67.6 kB With provenance Details for voicesafekit-0.6.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for voicesafekit 0.6.0
File Interpreter ABI Platform Actions
voicesafekit-0.6.0-py3-none-any.whl Python 3 none any Details for voicesafekit-0.6.0-py3-none-any.whl

Total release size: 119.4 kB

Release files / voicesafekit-0.6.0.tar.gz

Download URL voicesafekit-0.6.0.tar.gz
Size 67.6 kB
Tags Source
SHA-256 checksum
How to use checksums
6eef9d0f50194a3dad7c49295f4a57edd8497c71980d28d252c47fa8018d2db2
BLAKE2b-256 checksum
How to use checksums
59b35268530201c05b1e8580d59c5e13eb6b9188c0102de53aa9e5a68a0982c4
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 7, 2026.

Transparency log

Release files / voicesafekit-0.6.0-py3-none-any.whl

Download URL voicesafekit-0.6.0-py3-none-any.whl
Size 51.8 kB
Tags Python 3
SHA-256 checksum
How to use checksums
7980feb764db9f93940f3c0d18754fee22a4aebe3ca45bd6e5e60fac1f5697f4
BLAKE2b-256 checksum
How to use checksums
c5c6bbf3f88fab169d16bad8efa86e6bf853c938c33b784cd8ccfd2b511d210d
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 7, 2026.

Transparency log

Security for voicesafekit 0.6.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

Both files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for voicesafekit-0.6.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 7, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.6.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page