Skip to content

Security: snehamohandaas/voice-genius

Security

SECURITY.md

Security Policy

Reporting a vulnerability

Please do not open a public issue for a security problem. A public report tells everyone about the flaw before there is a fix available.

Report privately instead, using either:

  1. GitHub private vulnerability reporting — go to the repository's Security tab and choose Report a vulnerability. This opens a private advisory visible only to the maintainers. (Repository owners: enable this under Settings → Code security and analysis → Private vulnerability reporting.)
  2. Email the maintainers at the address listed on the repository's GitHub profile, with SECURITY in the subject line.

Please include:

  • what the issue is and which component is affected;
  • the steps to reproduce it, or a proof of concept;
  • the impact you believe it has;
  • the version or commit you tested.

What to expect: an acknowledgement within 5 working days, an assessment within 10 working days, and credit in the fix notes unless you would rather stay anonymous. This is a volunteer-maintained project, so there is no paid bounty.

Supported versions

Only the latest commit on main receives security fixes.

Safe-deployment checklist

The defaults in this repository are chosen so a fresh clone runs locally with no configuration. Those same defaults are not safe on a public network. Work through this list before exposing an instance to the internet.

Configuration

  • DEBUG=false. With debug on, Django serves full tracebacks — including settings and local variables — to anyone who triggers an error.
  • DJANGO_SECRET_KEY is set to a fresh random value and is not shared between environments. Generate one with: python -c "from django.core.management.utils import get_random_secret_key as k; print(k())". With DEBUG=false the app refuses to start without it; it will never fall back to the development key.
  • ALLOWED_HOSTS lists real hostnames. * is rejected outright when DEBUG=false.
  • CORS_ALLOWED_ORIGINS lists only your own frontend origins. The API sends credentials, so a wildcard would let any website call it on a visitor's behalf. CORS_ALLOW_ALL_ORIGINS is never enabled by this code.
  • RECAPTCHA_SECRET_KEY is set. Required when DEBUG=false. Without it the contact form is an open, unauthenticated write endpoint.
  • OPENAI_API_KEY is set, scoped to this application, and has a spend limit. Every recording that reaches the API costs money.

Transport and headers

  • Terminate TLS in front of the app. With DEBUG=false the app sets HSTS, SESSION_COOKIE_SECURE, CSRF_COOKIE_SECURE, X-Frame-Options: DENY and content-type nosniff automatically.
  • If a proxy already redirects HTTP to HTTPS, set SECURE_SSL_REDIRECT=false to avoid a redirect loop.
  • Run python manage.py check --deploy and resolve every warning.

Runtime

  • Use the provided Dockerfiles, or an equivalent WSGI server. Never deploy manage.py runserver or the Vite dev server. Both images run as a non-root user.
  • Do not serve MEDIA_ROOT from Django in production. The URL pattern for it is registered only when DEBUG is on.
  • Restrict /admin/ by network, or disable it if unused. There is no rate limiting on the admin login.
  • Tune API_ANON_THROTTLE_RATE (default 30/minute). The audio endpoint is unauthenticated and each call spends money at OpenAI.
  • Cap upload size at the proxy too. AUDIO_MAX_UPLOAD_BYTES is enforced by the app, but a proxy limit stops large bodies earlier.

Personal data

Deploying this application means processing other people's personal data. Know what it collects before you go live.

What Where it is stored Retention
Contact form: first name, last name, company, email, message, product interests Database table voiceApp_appformsubmission Indefinite — no automatic deletion
Voice recording (latest_input.mp3) MEDIA_ROOT/<session-uuid>/ AUDIO_RETENTION_HOURS, default 24h
Generated speech (latest_output.mp3) MEDIA_ROOT/<session-uuid>/ AUDIO_RETENTION_HOURS, default 24h
Transcript history — the last AUDIO_HISTORY_LENGTH transcriptions and responses MEDIA_ROOT/<session-uuid>/history.json AUDIO_RETENTION_HOURS, default 24h

Notes on the design:

  • The session id is not an account. It is a random UUID minted in the browser's localStorage and rotated every 24 hours. It groups one visitor's recordings; it is not tied to a name, an email, or an IP address.
  • The server does not log IP addresses itself. Your reverse proxy almost certainly does. That log is personal data too — set a retention period on it.
  • Recordings are sent to OpenAI for transcription and text generation, and the generated text is sent to Google Translate's TTS endpoint by gTTS. Both are third-party processors. Say so in your privacy notice.
  • The contact form transmits a token to Google for reCAPTCHA verification.

Meeting a deletion request

Session data expires on its own, and the sweep runs on every request. To purge sooner, or to run it from a scheduled job:

python manage.py purge_sessions          # delete anything past retention
python manage.py purge_sessions --all    # delete every stored session

For the strongest posture, set AUDIO_PERSIST_RECORDINGS=false. Recordings are then processed and deleted immediately, leaving only the rolling text history.

Contact form submissions have no automatic expiry. If you deploy this, decide on a retention period and delete rows on that schedule — via the Django admin, or a scheduled AppFormSubmission.objects.filter(submitted_at__lt=...).delete().

Known limitations

These are design constraints of the project, not undisclosed vulnerabilities:

  • Both API endpoints are unauthenticated. Protection is rate limiting and reCAPTCHA, not identity. Anyone who can reach the audio endpoint can spend your OpenAI quota.
  • The session id is client-supplied. It is validated as a UUID, so it cannot be used to escape MEDIA_ROOT, but a caller who guesses or replays another session's UUID would read that session's transcript history.
  • VITE_* variables ship inside the JavaScript bundle and are readable by anyone. This is correct for the reCAPTCHA site key; never put a secret there.

There aren't any published security advisories