Appearance
Security Deployment Guide
This guide covers production deployment security hardening for self-hosted RustChat instances. Follow these steps before exposing RustChat to the internet.
Target Audience
System administrators and operators deploying RustChat in production environments.
TLS/HTTPS Configuration
RustChat does not terminate TLS itself. Run it behind a reverse proxy (nginx, Caddy, Traefik, or cloud load balancer).
Reverse Proxy Setup
- Enforce HTTPS at the edge. The proxy handles TLS; RustChat runs internally on HTTP.
- Set
RUSTCHAT_SITE_URLto the HTTPS URL. Even though RustChat runs on HTTP internally, this URL is what browsers see.bashRUSTCHAT_SITE_URL=https://chat.example.com - Forward real client IPs. Configure your proxy to pass
X-Forwarded-ForandX-Forwarded-Protoheaders so RustChat can enforce origin checks correctly. - Disable direct HTTP access. Firewall rules should block external traffic to the RustChat port (default
3000). Only the reverse proxy should reach it.
Recommended nginx Snippet
nginx
server {
listen 443 ssl http2;
server_name chat.example.com;
ssl_certificate /path/to/cert.pem;
ssl_certificate_key /path/to/key.pem;
ssl_protocols TLSv1.3;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}Secret Management
Required Secrets
| Variable | Minimum Length | Purpose |
|---|---|---|
RUSTCHAT_JWT_SECRET | 32 chars | Signs all JWT tokens |
RUSTCHAT_ENCRYPTION_KEY | 32 chars, high entropy | Encrypts sensitive at-rest data |
RUSTCHAT_JWT_ISSUER | Non-empty string | JWT iss claim; required in production |
RUSTCHAT_JWT_AUDIENCE | Non-empty string | JWT aud claim; required in production |
RUSTCHAT_S3_SECRET_KEY | Strong random | S3-compatible storage authentication |
RUSTFS_SECRET_KEY | Strong random | RustFS root credentials |
PUSH_PROXY_AUTH_KEY | 32 chars | Backend-to-push-proxy authentication |
Generating Secrets
bash
# JWT secret (high entropy, min 32 chars)
openssl rand -base64 48
# Encryption key (min 32 chars, high entropy)
openssl rand -base64 48
# S3 access/secret keys
openssl rand -hex 16 # access key
openssl rand -base64 32 # secret keySecret Rotation
- JWT Secret: Rotating this invalidates all active sessions. Plan rotation during maintenance windows. There is no built-in key versioning; a hard cutover is required.
- Encryption Key: Rotating this requires re-encrypting existing data. Back up the database before attempting rotation.
- S3/RustFS Keys: Rotate via your object storage admin console, then update
.envand restart RustChat.
Startup Validation
RustChat validates secrets on startup:
In Production (RUSTCHAT_ENVIRONMENT=production):
- Fails to start if secrets are weak
- Checks minimum length (32 chars)
- Checks entropy (no repeated patterns)
- Checks for common weak patterns
In Development:
- Logs warnings for weak secrets
- Allows startup with weak secrets
Storage Best Practices
- Never commit
.envor secret files. - Use a secrets manager (HashiCorp Vault, AWS Secrets Manager, 1Password Secrets Automation) or Docker secrets in swarm mode.
- Set file permissions on
.envto0600.
Environment-Based Security Constraints
The default environment is production. Only override it to development on explicitly non-production hosts.
bash
RUSTCHAT_ENVIRONMENT=productionEffects in production mode:
- HTTP origins in
RUSTCHAT_CORS_ALLOWED_ORIGINSare rejected (HTTPS only). RUSTCHAT_ALLOW_DEV_CORSis rejected; permissive CORS cannot be enabled in production.- Stricter request validation on authentication flows.
- Error responses may omit internal details.
Rate Limiting Configuration
Enable rate limiting to protect against brute-force attacks:
bash
RUSTCHAT_SECURITY_RATE_LIMIT_ENABLED=true
# Per-account login sliding window (default: 10 requests/min per account)
RUSTCHAT_SECURITY_RATE_LIMIT_AUTH_PER_MINUTE=10RustChat applies two independent rate-limiting layers:
IP-based middleware limits — hardcoded per-IP fixed windows for unauthenticated endpoints:
Endpoint Class Limit Auth endpoints (login, register, verification, password reset) 10 requests per 15 minutes WebSocket connections 20 connections per minute Registration 5 requests per 15 minutes Password reset 3 requests per 15 minutes File uploads 30 requests per 15 minutes Search 60 requests per minute Per-account login sliding window — configured by
RUSTCHAT_SECURITY_RATE_LIMIT_AUTH_PER_MINUTEand enforced inside the auth handlers. This throttles individual accounts independently of the IP-based limits.
RUSTCHAT_SECURITY_RATE_LIMIT_AUTH_PER_MINUTE does not tune the IP-based middleware values; it only adjusts the per-account login sliding window.
If you run multiple backend instances behind a load balancer, the Redis-backed counters are shared automatically through RustChat's existing Redis connection.
Rate Limit Responses
When rate limited, the API returns a 429 Too Many Requests status with a Retry-After header (when the backend knows the window length):
http
HTTP/1.1 429 Too Many Requests
Retry-After: 900
{
"error": {
"code": "TOO_MANY_REQUESTS",
"message": "Too many authentication attempts. Please try again later.",
"details": null
}
}The exact code may be TOO_MANY_REQUESTS (IP-based middleware) or RATE_LIMIT_EXCEEDED (other rate-limit paths). Responses do not include X-RateLimit-* headers or a JSON retry_after field.
CORS and Origin Enforcement
Restrict CORS to exactly the origins that serve your RustChat frontend:
bash
# Production: HTTPS only, comma-separated, no spaces
RUSTCHAT_CORS_ALLOWED_ORIGINS=https://chat.example.com,https://app.example.com- Never use
*in production. - Do not include
http://origins. - If you embed RustChat in an iframe or mobile app WebView, add those exact origins.
Permissive Development CORS
RUSTCHAT_ALLOW_DEV_CORS enables permissive CORS (Access-Control-Allow-Origin: *) that bypasses RUSTCHAT_CORS_ALLOWED_ORIGINS. It must be explicitly set to true to enable this behavior, and it is rejected when RUSTCHAT_ENVIRONMENT=production.
bash
# Only for local development
RUSTCHAT_ALLOW_DEV_CORS=falseS3 Access Key Security
- Do not reuse default/demo credentials. Generate unique keys per deployment.
- Scope bucket policies narrowly. RustChat needs
PutObject,GetObject, andDeleteObjectonly on its upload bucket. - Enable bucket versioning or object locking if regulatory requirements apply.
- Use separate credentials for RustFS and RustChat S3 access when possible.
- Enable TLS on S3 endpoints. If using RustFS, run it with a valid TLS certificate or access it through an internal TLS-terminating proxy.
- Configure the S3 bucket as private. RustChat does not require a public bucket because all file access, downloads, and attachments are served via authenticated proxy endpoints or transient presigned URLs. The bucket itself must never be exposed directly to the public internet.
Database Security (PostgreSQL)
Connection Encryption
- Enable SSL/TLS on PostgreSQL. Set
ssl = oninpostgresql.confand provide server certificates. - Use
sslmode=requireorverify-fullin the connection string:bashRUSTCHAT_DATABASE_URL=postgres://rustchat:PASSWORD@db.example.com:5432/rustchat?sslmode=require - Client certificate verification (optional, high-security environments):bash
RUSTCHAT_DATABASE_URL=postgres://rustchat@db.example.com:5432/rustchat?sslmode=verify-full&sslcert=/certs/client.crt&sslkey=/certs/client.key
Database Hardening
- Create a dedicated PostgreSQL user with minimal privileges (no superuser, no createdb).
- Disable external network access to PostgreSQL; restrict to the RustChat backend subnet.
- Enable PostgreSQL logging for connection attempts and slow queries.
- Run
pg_hba.confwithmd5orscram-sha-256authentication, nevertrust.
Redis Security
Authentication
If your Redis instance supports AUTH, include the password in the URL:
bash
RUSTCHAT_REDIS_URL=redis://:PASSWORD@redis.example.com:6379/0TLS
For Redis 6+ with TLS:
bash
RUSTCHAT_REDIS_URL=rediss://:PASSWORD@redis.example.com:6379/0Ensure your Redis server has tls-port and tls-cert-file configured.
Network Hardening
- Bind Redis to a private interface or Unix socket.
- Disable the
FLUSHALLandCONFIGcommands viarename-commandif they are not needed. - Enable Redis ACLs to restrict the RustChat user to the minimum command set.
File Upload Security
- Size Limits: Set upload size limits at the reverse proxy level (nginx
client_max_body_size) to prevent oversized uploads from reaching RustChat. - Type Validation: RustChat validates file MIME types and extensions for both multipart and resumable (TUS) uploads. Do not disable this.
- Resumable Uploads: Resumable uploads are validated against the same extension and content-type allowlist as multipart uploads.
- Storage Isolation: Uploaded files should reside in a dedicated S3 bucket, separate from other application data.
- Malware Scanning: Consider integrating a virus scanner (ClamAV, commercial API) at the S3 ingress or via Lambda-style triggers.
- Content Security: Serve uploaded files from a separate subdomain or with strict
Content-Disposition: attachmentheaders to reduce XSS vectors.
Channel Call Permissions
Toggling channel calls (enabling or disabling calls in a channel) requires channel-management permission. This prevents non-administrators from changing a channel's call configuration.
Outgoing Webhooks and Slash Commands
Outgoing webhook and slash-command URLs are validated when created or updated and are re-validated at request time before any outbound request is issued.
Validation behavior:
- URLs must resolve to allowed IP families/ranges (loopback, link-local, and multicast addresses are blocked).
- DNS resolution is performed at request time to prevent DNS-rebinding SSRF attacks.
- HTTP redirects are disabled; a redirect response fails the request instead of following it.
- Invalid or unsafe URLs are rejected with a clear error.
Administrators should review configured webhook and slash-command URLs in the admin console and ensure they point only to trusted, production-owned endpoints.
AI Agents and Knowledge Base Security
AI Agents are optional and should be enabled only after provider credentials, data access, and operational ownership are defined.
Provider and Tool Secrets
- Store
RUSTCHAT_OPENAI_API_KEY,OPENAI_API_KEY, andTAVILY_API_KEYin the same secret manager as other production secrets. - Do not place provider keys in agent prompts, knowledge documents, or channel messages.
- Rotate provider keys when an administrator with access leaves the organization.
- Disable optional tools by removing their environment variables and restarting the backend.
Prompt and Data Boundaries
- Treat system prompts, recent channel context, memories, retrieved knowledge chunks, and tool results as data that may be sent to an external LLM provider.
- Assign agents only to channels they must read.
- Assign knowledge bases only to agents that need that content.
- Keep highly sensitive document sets in separate knowledge bases so they can be reviewed and assigned independently.
- Review RustShare sync source scope before enabling recursive folder sync.
RAG Storage
Knowledge base document blobs use S3-compatible storage. Metadata, chunks, and embeddings live in PostgreSQL with pgvector.
- Protect the S3 bucket used for knowledge documents with TLS and narrowly scoped credentials.
- Back up PostgreSQL and object storage together so document metadata and blobs remain consistent.
- Remember that embeddings may reveal semantic information about source documents; protect database backups accordingly.
Monitoring
Monitor:
- agent response volume and token usage spikes
- negative feedback rates
- tool invocation errors
- knowledge indexing failures
- unexpected agent activity in sensitive channels
WebSocket Token Transport Security
Query-string WebSocket tokens have been removed. WebSocket connections authenticate through secure transports such as the Authorization header, the WebSocket subprotocol token fallback, or the authenticated v4 handshake message. This prevents tokens from appearing in:
- Server access logs
- Browser history
- Referrer headers
- CDN/proxy caches
Ensure your reverse proxy forwards authentication headers and preserves WebSocket upgrade headers.
OAuth Token Delivery
Only secure mode is supported: RUSTCHAT_SECURITY_OAUTH_TOKEN_DELIVERY=cookie.
- One-time exchange code in URL:
/login?code=xyz - Client exchanges code for token via
POST /api/v1/oauth2/exchange - Token never appears in redirect URLs
Audit Logging
RustChat emits structured logs via RUSTCHAT_LOG_LEVEL. For security auditing:
- Set log level to
infoorwarnminimum.bashRUSTCHAT_LOG_LEVEL=info - Forward logs to a SIEM or central log aggregator. Export from journald, Docker logging driver, or file tailing.
- Key events to monitor:
- Failed login attempts (brute-force detection)
- JWT validation failures (token replay or tampering)
- Rate limit hits (DoS indicators)
- Permission denied errors (privilege escalation attempts)
- Admin actions (user deletions, role changes)
- File upload/download anomalies
- Agent configuration changes, tool registration, feedback spikes, and knowledge indexing failures
Sample Promtail/Loki Labels
yaml
- job_name: rustchat
static_configs:
- targets:
- localhost
labels:
job: rustchat
environment: production
__path__: /var/log/rustchat/*.logMonitoring and Alerting
Security Events to Monitor
| Event | Severity | Action |
|---|---|---|
| Rate limit exceeded | Warning | Monitor for patterns |
| Invalid OAuth state | Warning | Possible CSRF attempt |
| Weak secret detected | Critical | Rotate immediately |
| WebSocket auth failure | Info | Normal for expired tokens |
| Exchange code reuse | Warning | Possible token theft |
| Agent token or usage spike | Warning | Check prompt, channel assignment, and provider key usage |
| Knowledge indexing failure | Warning | Check S3, pgvector, embedding provider, and sync source credentials |
Log Redaction
Configure your reverse proxy to redact sensitive headers:
nginx
# nginx - don't log tokens
log_format security '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
access_log /var/log/nginx/access.log security;Backup Encryption
- Encrypt database backups at rest. Use
pg_dumpwith AES encryption or store backups in an encrypted object storage bucket.bashpg_dump -Fc rustchat | gpg --symmetric --cipher-algo AES256 -o rustchat-$(date +%F).dump.gpg - Encrypt S3 bucket backups with server-side encryption (SSE-S3, SSE-KMS, or client-side encryption).
- Store backup encryption keys separately from the production environment.
- Test restore procedures quarterly. An encrypted backup you cannot restore is worthless.
- Automate backup retention with lifecycle policies. Keep encrypted off-site copies for disaster recovery.
Frontend Container User
The frontend container runs as the unprivileged rustchat user (UID/GID created in docker/frontend.Dockerfile). It does not run as root.
Verify at runtime:
bash
docker exec <frontend-container> id
# Expected: uid=... rustchat gid=... rustchatQuick Production Checklist
- [ ]
RUSTCHAT_ENVIRONMENT=production - [ ]
RUSTCHAT_SITE_URLuseshttps:// - [ ]
RUSTCHAT_CORS_ALLOWED_ORIGINSuseshttps://only - [ ]
RUSTCHAT_ALLOW_DEV_CORSisfalseor unset in production - [ ]
RUSTCHAT_SECURITY_OAUTH_TOKEN_DELIVERY=cookie - [ ]
RUSTCHAT_SECURITY_RATE_LIMIT_ENABLED=true - [ ]
RUSTCHAT_JWT_ISSUERis set to a non-empty value - [ ]
RUSTCHAT_JWT_AUDIENCEis set to a non-empty value - [ ] All secrets are unique, random, and >= 32 characters
- [ ] Reverse proxy enforces TLS 1.3 and forwards real IPs
- [ ] PostgreSQL uses SSL/TLS and dedicated user
- [ ] Redis uses AUTH and TLS (if networked)
- [ ] S3-compatible storage uses unique credentials and TLS
- [ ] Outgoing webhook and slash-command URLs point only to trusted endpoints
- [ ] Frontend container runs as the
rustchatnon-root user - [ ] AI provider and tool keys are stored as production secrets if agents are enabled
- [ ] Agent channel assignments and knowledge base assignments have been reviewed
- [ ] PostgreSQL
pgvectoris enabled before RAG knowledge bases are used - [ ]
.envfile permissions are0600 - [ ] Backups are encrypted and tested
- [ ] Logs are forwarded to a central aggregator
Migration from Insecure Defaults
If currently running with insecure defaults:
- Immediate (P0): Rotate to strong secrets and verify
RUSTCHAT_SECURITY_OAUTH_TOKEN_DELIVERY=cookie. - Cleanup: Query-string WebSocket tokens and URL/header OAuth token delivery are already rejected at startup. Treat any remaining client cleanup as removing unsupported behavior, not an optional rollout.
Contact your security team if you need assistance with the migration.
For zero-trust architectural guidance, see docs/security-zero-trust-guide.md.