Appearance
Security Hardening
This guide covers production hardening steps beyond the default installation. These apply to all self-hosted deployments — VMs, bare metal, Kubernetes, or any other platform.
Network Isolation
Restrict Policy Engine to Internal Traffic Only
The Policy Engine should never be directly reachable from the internet. Only the Proxy Daemon and Dashboard backend should be able to call it.
Apply firewall rules so that port 3002 is only reachable from internal services.
bash
# Example: UFW rule to restrict policy-engine to local services only
ufw deny 3002
ufw allow from 127.0.0.1 to any port 3002Expose Only the Dashboard
Only port 3000 (Dashboard) should be reachable from users on the network. It should be behind a reverse proxy or load balancer that terminates TLS. The Proxy Daemon (port 3001) listens only on 127.0.0.1 by default and is not exposed to the network.
TLS / HTTPS
All traffic must be TLS 1.2+ in transit. Use a reverse proxy in front of each service:
- Place nginx or Caddy in front of each service
- Use Let's Encrypt via Certbot or Caddy's built-in ACME support
- Configure your reverse proxy to reject TLS 1.0 and 1.1
nginx
# Minimum nginx TLS config
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:...;
ssl_prefer_server_ciphers off;Set TRUST_PROXY=1 on the Policy Engine and API Gateway when behind a proxy so that X-Forwarded-For is trusted for rate limiting and audit logging.
Secrets Management
Required Secrets
Never commit secrets to your repository or put them in plain environment files.
| Variable | Service | Notes |
|---|---|---|
JWT_SECRET | Policy Engine | Minimum 32 random bytes. Rotate if compromised — all sessions will be invalidated. |
DATABASE_URL | Policy Engine | PostgreSQL connection string with credentials |
REDIS_URL | Policy Engine | Include password if Redis auth is configured |
INTERNAL_SERVICE_SECRET | Proxy Daemon + Policy Engine | Must match on both services — auto-generated by the installer |
Generate strong secrets:
bash
openssl rand -hex 32 # JWT_SECRET, INTERNAL_SERVICE_SECRET
openssl rand -base64 48 # longer secretsRotating JWT_SECRET
Rotating JWT_SECRET immediately invalidates all active user sessions (all users are logged out). Plan for this during low-traffic windows:
- Generate a new secret
- Update the env var on the Policy Engine
- Redeploy — all sessions are invalidated
- Users log back in and receive new tokens
Database Security
PostgreSQL Access Controls
Restrict database access to only the Policy Engine:
sql
-- Create a least-privilege role
CREATE ROLE bitdrip_app LOGIN PASSWORD '<strong-password>';
GRANT CONNECT ON DATABASE bitdrip TO bitdrip_app;
GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA public TO bitdrip_app;
REVOKE ALL ON ALL TABLES IN SCHEMA public FROM PUBLIC;Enable SSL for Database Connections
Append ?sslmode=require to your DATABASE_URL:
DATABASE_URL=postgresql://bitdrip_app:password@host:5432/bitdrip?sslmode=requireRestrict pg_hba.conf
Only allow connections from the Policy Engine's IP. Reject all other hosts.
Rate Limiting
Rate limiting is built into the Proxy Daemon and Policy Engine. Default limits:
| Limit | Default |
|---|---|
| Policy evaluations per user per minute | 60 |
| Login attempts per IP per 15 minutes | 5 |
| API key requests per hour | 1000 |
Adjust via environment variables RATE_LIMIT_EVAL, RATE_LIMIT_LOGIN, RATE_LIMIT_API_KEY. Set them lower in high-security environments.
Access Control
Least-Privilege Roles
Assign users the minimum role they need:
| Role | What they can do |
|---|---|
super_admin | Full access — assign only to IT/security leads |
org_admin | Manage users, configure policies, view all audit logs |
user | View their own audit events and block statistics |
readonly | View-only access to reports and dashboards |
Audit super_admin accounts quarterly. Remove access when employees leave.
API Key Scopes
API keys are scoped. Issue the narrowest scope needed:
evaluate — Submit content for evaluation (proxy daemon, SDK)
admin — Full management API access
audit:read — Read audit logs only
policies:write — Create and modify policy rulesRotate API keys every 90 days. Set expiry dates when issuing keys.
Audit Log Integrity
The audit log is protected by a SHA-256 hash chain. Each log entry includes a hash of the previous entry's hash combined with the current entry's content. Tampering with any entry breaks the chain.
To verify chain integrity, export your audit log and run the verification script:
bash
# Export and verify
curl -H "Authorization: Bearer $ADMIN_TOKEN" \
http://your-policy-engine:3002/api/v1/audit/export > audit.json
# The Policy Engine ships a verification utility
node scripts/verify-audit-chain.js audit.jsonA clean chain outputs: ✓ Chain integrity verified — N entries, no gaps detected
Container Image Security
Verify Image Signatures
All BitDrip Docker images are signed with cosign via Sigstore. Verify before deploying:
bash
cosign verify ghcr.io/getbitdrip/policy-engine:2.2.0 \
--certificate-identity-regexp="https://github.com/getbitdrip/BitDrip-app" \
--certificate-oidc-issuer="https://token.actions.githubusercontent.com"Reject unsigned or unverified images in your deployment pipeline.
Run as Non-Root
All official BitDrip images run as UID 1001 (bitdrip user) by default. Do not override this with --user root. If your platform requires a specific UID, rebuild from the provided Dockerfile.
Updates and Patch Management
Subscribe to GitHub security advisories for BitDrip. When a patch is released:
- Pull the new image:
docker pull ghcr.io/getbitdrip/policy-engine:latest - Verify the signature
- Deploy with a rolling restart to maintain availability
- Confirm the
/healthendpoint returns200before removing old containers
Dependency vulnerabilities are automatically scanned by GitHub Dependabot. Keep your deployment up to date — run npm audit --audit-level=high in your environment if you build from source.
