Skip to main content

Security Hardening

This guide covers security best practices for production OpenTranscribe deployments, including authentication hardening, network security, data protection, and compliance considerations.

Security Architecture

Security Checklist

Use this checklist before deploying to production.

Pre-Deployment

  • Change all default passwords (database, MinIO, Redis, admin account)
  • Generate a strong JWT_SECRET (minimum 64 characters): openssl rand -hex 64
  • Generate a strong ENCRYPTION_KEY: openssl rand -hex 32
  • Set DEBUG=false in .env
  • Configure TLS certificates for HTTPS
  • Review and restrict CORS origins (CORS_ORIGINS)
  • Set ALLOWED_HOSTS to your domain(s)
  • Remove or restrict API documentation endpoint (/docs) in production

Authentication

  • Enable MFA enforcement for admin accounts (at minimum)
  • Configure password policy (minimum 12 characters, complexity requirements)
  • Set account lockout threshold (recommended: 5 failed attempts)
  • Configure session timeout (recommended: 30 minutes for sensitive environments)
  • Enable audit logging

Infrastructure

  • Restrict exposed ports to only what is necessary (typically 443 only)
  • Configure firewall rules for Docker networks
  • Enable container health checks
  • Set up log aggregation and monitoring
  • Schedule regular backups with encryption
  • Plan credential rotation schedule

Authentication Hardening

MFA Enforcement

OpenTranscribe supports TOTP-based multi-factor authentication compatible with standard authenticator apps (Google Authenticator, Microsoft Authenticator, Authy).

Configuration options:

SettingDescriptionRecommended
MFA required for all usersEvery user must enroll in MFAHigh-security environments
MFA required for admins onlyOnly admin-role users must enrollStandard deployments
MFA optionalUsers can opt inDevelopment only

Configure in Admin > Settings > Authentication > MFA Policy.

Backup codes are generated during enrollment (8-character alphanumeric, XXXX-XXXX format). They are stored hashed with PBKDF2-SHA256 and are single-use.

Password Policies

Configure password requirements to match your organization's security standards:

# .env configuration
PASSWORD_MIN_LENGTH=12 # Minimum password length
PASSWORD_REQUIRE_UPPERCASE=true # Require uppercase letter
PASSWORD_REQUIRE_LOWERCASE=true # Require lowercase letter
PASSWORD_REQUIRE_DIGIT=true # Require number
PASSWORD_REQUIRE_SPECIAL=true # Require special character
PASSWORD_HISTORY_COUNT=12 # Prevent reuse of last N passwords
PASSWORD_EXPIRY_DAYS=90 # Force password change (0 = disabled)

Passwords are hashed using bcrypt with SHA-256 pre-hash (overcomes bcrypt's 72-byte limit). For FIPS environments, PBKDF2-SHA256 with 600,000 iterations is used instead.

Account Lockout

Protects against brute-force attacks:

ACCOUNT_LOCKOUT_THRESHOLD=5         # Lock after N failed attempts
ACCOUNT_LOCKOUT_DURATION=900 # Lockout duration in seconds (15 min)
ACCOUNT_LOCKOUT_RESET_AFTER=1800 # Reset counter after N seconds (30 min)

Lockout events are recorded in the audit log. Administrators can manually unlock accounts through the admin panel.

Session Management

  • Access tokens: Short-lived JWT tokens (configurable, default 15 minutes)
  • Refresh tokens: Rotated on each use, preventing token replay
  • Session invalidation: All sessions terminated on password change
  • Concurrent sessions: Optionally limit active sessions per user

Network Security

Port Exposure

In production, expose only the minimum required ports:

PortServiceExposure
443NGINX (HTTPS)Public
80NGINX (HTTP redirect)Public (redirect to 443 only)
5432PostgreSQLInternal only
6379RedisInternal only
9000/9001MinIOInternal only
9200OpenSearchInternal only

All internal services should be accessible only through Docker's internal network, not bound to the host.

Docker Network Isolation

OpenTranscribe uses Docker networks to isolate services. In production, ensure:

# docker-compose.prod.yml - services should NOT expose ports to host
services:
postgres:
# Remove "ports:" mapping in production
# Access only through Docker network
redis:
# Remove "ports:" mapping in production
opensearch:
# Remove "ports:" mapping in production

LLM Firewall (Self-Hosted Models)

When running vLLM or Ollama on the host machine, Docker containers need firewall rules to reach the LLM server.

Restrictive approach -- allow only Docker containers:

# Allow vLLM only from Docker network
sudo ufw allow from 172.17.0.0/16 to any port 8000 proto tcp comment 'vLLM from Docker'

# Allow Ollama only from Docker network
sudo ufw allow from 172.17.0.0/16 to any port 11434 proto tcp comment 'Ollama from Docker'

Alternative -- use Docker bridge IP in configuration:

Instead of localhost, configure the LLM endpoint as:

  • http://172.17.0.1:8000/v1 (Docker bridge gateway on Linux)
  • http://host.docker.internal:8000/v1 (Docker Desktop on macOS/Windows)

Or add extra_hosts to relevant services in docker-compose.yml:

services:
backend:
extra_hosts:
- host.docker.internal:host-gateway
celery-nlp-worker:
extra_hosts:
- host.docker.internal:host-gateway
tip

Both the backend and celery-nlp-worker containers need access to the LLM server. The Settings UI test runs from backend, but actual summarization runs from celery-nlp-worker.

TLS Configuration

For production deployments with NGINX:

# Start with TLS (production mode with NGINX)
./opentr.sh start prod --with-pki

Ensure TLS certificates are:

  • From a trusted CA (not self-signed) for public deployments
  • Renewed before expiration (automate with certbot or similar)
  • Using TLS 1.2 or higher (TLS 1.0 and 1.1 should be disabled)

Data Protection

Encryption at Rest

MinIO Storage: Configure server-side encryption for stored media files:

# .env configuration
MINIO_KMS_SECRET_KEY=your-256-bit-key # AES-256 encryption key

MinIO supports AES-256-GCM for server-side encryption of objects (transcripts, audio/video files).

Database: Sensitive fields (API keys, TOTP secrets, LLM provider credentials) are encrypted in the database using AES-256-GCM with:

  • 256-bit key derived via PBKDF2-SHA256
  • 96-bit random nonce per encryption
  • 128-bit authentication tag

Data format: v3:base64(salt):base64(nonce):base64(ciphertext+tag)

PostgreSQL: Enable PostgreSQL's built-in encryption for the data directory if required by your compliance framework.

Encryption in Transit

All inter-service communication should use TLS in production:

  • Client to NGINX: TLS 1.2+ (configured in NGINX)
  • NGINX to backend: Internal Docker network (trusted)
  • Backend to PostgreSQL: Enable sslmode=require in database connection string
  • Backend to MinIO: Configure MinIO with TLS certificates
  • Backend to OpenSearch: Enable HTTPS for OpenSearch

Backup Encryption

# Encrypted backup
./opentr.sh backup
gpg --symmetric --cipher-algo AES256 backups/backup_*.sql

# Encrypted restore
gpg --decrypt backups/backup_*.sql.gpg | ./opentr.sh restore -

API Security

Rate Limiting

Authentication endpoints are rate-limited to prevent brute-force attacks:

EndpointLimitScope
LoginConfigurablePer-IP and per-user
RegistrationConfigurablePer-IP
Password ResetConfigurablePer-IP
API endpointsConfigurablePer-user token

CORS Configuration

Restrict origins to your domain(s):

# .env - only allow your domain
CORS_ORIGINS=https://transcribe.yourdomain.com

Never use * (wildcard) in production.

Content Security Policy

When using NGINX in production, configure CSP headers:

add_header Content-Security-Policy "default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'; img-src 'self' data: blob:; connect-src 'self' wss:; media-src 'self' blob:;" always;
add_header X-Frame-Options DENY always;
add_header X-Content-Type-Options nosniff always;
add_header Referrer-Policy strict-origin-when-cross-origin always;

Input Validation

OpenTranscribe validates all inputs through Pydantic schemas on the backend. File uploads are validated for:

  • File type (allowed MIME types only)
  • File size (configurable maximum, default 15GB)
  • Filename sanitization

Container Security

Non-Root Execution

All backend containers run as appuser (UID 1000, GID 1000), not root:

  • Reduces impact of container escape vulnerabilities
  • Compliant with security scanning tools (Trivy, Snyk)
  • User groups: appuser, video (for GPU access)

If you encounter permission issues with model cache directories:

./scripts/fix-model-permissions.sh

Image Security

  • Multi-stage builds: Production images use multi-stage Dockerfiles to minimize attack surface
  • Minimal base images: Only necessary runtime dependencies are included
  • No secrets in images: All secrets are passed via environment variables or mounted files
  • Regular updates: Base images should be rebuilt periodically to pick up security patches

Image Scanning

Scan images before deployment:

# Using Trivy
trivy image davidamacey/opentranscribe-backend:latest
trivy image davidamacey/opentranscribe-frontend:latest

# Using Docker Scout
docker scout cves davidamacey/opentranscribe-backend:latest

Audit and Compliance

Audit Logging

All authentication events are logged for security monitoring:

  • Login attempts (success and failure) with IP address
  • Password changes and resets
  • MFA enrollment and removal
  • Account lockouts and unlocks
  • Session creation and termination
  • Administrative actions (user management, settings changes)

Log format supports integration with SIEM systems (Splunk, ELK, etc.).

Log Retention

Configure log retention based on your compliance requirements:

Compliance FrameworkMinimum Retention
SOC 21 year
HIPAA6 years
FedRAMP3 years
GDPRAs needed for purpose

FedRAMP/NIST Controls Mapping

OpenTranscribe includes features that map to NIST 800-53 controls:

ControlImplementation
AC-2 (Account Management)User management, role assignment, account disable
AC-7 (Unsuccessful Login Attempts)Account lockout after configurable threshold
AC-8 (System Use Notification)Classification banners (UNCLASSIFIED, CUI, SECRET, TOP SECRET)
AC-12 (Session Termination)Configurable session timeouts, auto-logout on inactivity
AU-2/AU-3 (Audit Events)Comprehensive audit logging with timestamps and source IPs
IA-2 (Identification and Authentication)Multi-factor authentication, PKI/CAC support
IA-5 (Authenticator Management)Password complexity, history, expiration policies
SC-12 (Cryptographic Key Establishment)PBKDF2 key derivation
SC-13 (Cryptographic Protection)AES-256-GCM, HS512 JWT signing
SC-28 (Protection of Information at Rest)Encrypted sensitive data fields

Classification banners are configurable in Admin > Settings > System > Classification Banner.

Secrets Management

Protecting the .env File

The .env file contains all sensitive configuration. Protect it:

# Restrict file permissions
chmod 600 .env
chown root:root .env

# Never commit to version control
# (.env is in .gitignore by default)

Credential Rotation

Establish a rotation schedule for all credentials:

CredentialRotation FrequencyHow to Rotate
JWT_SECRET90 daysUpdate in .env, restart backend (invalidates all sessions)
ENCRYPTION_KEYAnnuallyUpdate in .env, existing data auto-re-encrypted on access
Database password90 daysUpdate in PostgreSQL and .env, restart all services
MinIO credentials90 daysUpdate in MinIO and .env, restart all services
LLM API keysPer provider policyUpdate in Settings > AI > LLM Provider

Avoiding Secrets in Logs

OpenTranscribe redacts sensitive values from log output. When adding custom integrations, never log:

  • API keys or tokens
  • Passwords or password hashes
  • Encryption keys
  • User session tokens
  • TOTP secrets or backup codes

FIPS 140-3 Compliance

For government and high-security deployments, OpenTranscribe supports FIPS 140-3 compliant cryptographic operations.

Enabling FIPS Mode

# .env configuration
FIPS_VERSION=140-3
PBKDF2_ITERATIONS_V3=600000 # NIST SP 800-132 2024 recommendation
JWT_ALGORITHM_V3=HS512 # HMAC-SHA512 for JWT signing
ENCRYPTION_ALGORITHM_V3=AES-256-GCM # Authenticated encryption
FIPS_MIGRATION_MODE=compatible # Accept both old and new formats during transition
FIPS_VALIDATE_ENTROPY=true # Validate entropy sources

Algorithm Requirements

ComponentFIPS 140-2 (Legacy)FIPS 140-3Migration
Password HashingPBKDF2-SHA256 (210k iter)PBKDF2-SHA256 (600k iter)Auto-upgrade on login
Symmetric EncryptionFernet (AES-128-CBC)AES-256-GCMAuto-upgrade on access
JWT SigningHS256HS512Dual verification during transition
Token HashingSHA-256SHA-512Auto-upgrade on issuance
MFA Backup CodesbcryptPBKDF2-SHA256 (600k iter)Regeneration required

Migration Process

  1. Set FIPS_MIGRATION_MODE=compatible -- accepts both legacy and FIPS 140-3 formats
  2. Restart services -- ./opentr.sh restart-backend
  3. Monitor migration -- users are upgraded automatically on next login
  4. Switch to strict mode when all users have been upgraded: FIPS_MIGRATION_MODE=strict

TOTP Compatibility

TOTP uses SHA-1 by default per RFC 6238. This is FIPS-allowed because NIST SP 800-131A Rev. 2 permits SHA-1 for HMAC-based applications (SHA-1's collision weakness does not affect HMAC security). This ensures compatibility with standard authenticator apps.

For environments requiring SHA-256/SHA-512 TOTP (with compatible authenticator apps):

TOTP_ALGORITHM=SHA256  # or SHA512

Verification

Run the compliance verification script:

./scripts/verify-fips-140-3.sh

This checks password hashing algorithm and iterations, JWT signing algorithm, encryption algorithm, and token hash algorithm.

Vulnerability Reporting

If you discover a security vulnerability in OpenTranscribe:

  1. Do NOT create a public GitHub issue
  2. Email security concerns to the project maintainers (see the repository's SECURITY.md for contact information)
  3. Include: description of the vulnerability, steps to reproduce, potential impact, and suggested fixes if any

Response Timeline

SeverityInitial ResponseTarget Fix
Critical24-48 hours7 days
High72 hours14 days
Medium/Low1 week30 days

The project follows responsible disclosure practices and will credit researchers (with permission) in security advisories.