Skip to content

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 3002

Expose 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.

VariableServiceNotes
JWT_SECRETPolicy EngineMinimum 32 random bytes. Rotate if compromised — all sessions will be invalidated.
DATABASE_URLPolicy EnginePostgreSQL connection string with credentials
REDIS_URLPolicy EngineInclude password if Redis auth is configured
INTERNAL_SERVICE_SECRETProxy Daemon + Policy EngineMust 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 secrets

Rotating JWT_SECRET

Rotating JWT_SECRET immediately invalidates all active user sessions (all users are logged out). Plan for this during low-traffic windows:

  1. Generate a new secret
  2. Update the env var on the Policy Engine
  3. Redeploy — all sessions are invalidated
  4. 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=require

Restrict 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:

LimitDefault
Policy evaluations per user per minute60
Login attempts per IP per 15 minutes5
API key requests per hour1000

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:

RoleWhat they can do
super_adminFull access — assign only to IT/security leads
org_adminManage users, configure policies, view all audit logs
userView their own audit events and block statistics
readonlyView-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 rules

Rotate 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.json

A 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:

  1. Pull the new image: docker pull ghcr.io/getbitdrip/policy-engine:latest
  2. Verify the signature
  3. Deploy with a rolling restart to maintain availability
  4. Confirm the /health endpoint returns 200 before 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.

Released under the BitDrip Commercial License.