Hardening Guide
This guide covers security hardening measures for production Pinchy deployments. Pinchy is self-hosted — infrastructure security is your responsibility. These recommendations assume a Linux server with Docker, but most apply to any OS.
Reverse proxy with TLS
Section titled “Reverse proxy with TLS”Never expose the Pinchy Web service directly. Use a reverse proxy with TLS termination.
Caddy (recommended — automatic HTTPS)
Section titled “Caddy (recommended — automatic HTTPS)”pinchy.example.com { reverse_proxy localhost:7777}Caddy automatically provisions and renews Let’s Encrypt certificates.
server { listen 443 ssl http2; server_name pinchy.example.com;
ssl_certificate /etc/letsencrypt/live/pinchy.example.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/pinchy.example.com/privkey.pem;
# TLS hardening ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on;
# Security headers add_header Strict-Transport-Security "max-age=63072000; includeSubDomains" always; add_header X-Content-Type-Options nosniff always; add_header X-Frame-Options DENY always;
location / { proxy_pass http://127.0.0.1:7777; 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;
# WebSocket support (required for agent communication) proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; }}
server { listen 80; server_name pinchy.example.com; return 301 https://$host$request_uri;}Firewall rules
Section titled “Firewall rules”Only port 443 (HTTPS) needs to be exposed. Block everything else.
UFW (Ubuntu/Debian)
Section titled “UFW (Ubuntu/Debian)”ufw default deny incomingufw default allow outgoingufw allow sshufw allow 443/tcpufw enableiptables
Section titled “iptables”iptables -A INPUT -p tcp --dport 22 -j ACCEPTiptables -A INPUT -p tcp --dport 443 -j ACCEPTiptables -A INPUT -m state --state ESTABLISHED,RELATED -j ACCEPTiptables -A INPUT -i lo -j ACCEPTiptables -A INPUT -j DROPDisk encryption
Section titled “Disk encryption”PostgreSQL stores all Pinchy data on disk — user accounts, agent configurations, audit logs, and encrypted API keys. Encrypt the underlying volume.
Linux (LUKS)
Section titled “Linux (LUKS)”# Encrypt the partition (destructive — do this before deploying)cryptsetup luksFormat /dev/sdXcryptsetup open /dev/sdX pinchy-datamkfs.ext4 /dev/mapper/pinchy-datamount /dev/mapper/pinchy-data /var/lib/docker/volumesmacOS (FileVault)
Section titled “macOS (FileVault)”Enable FileVault in System Settings → Privacy & Security. This encrypts the entire startup disk, including Docker volumes.
Docker security
Section titled “Docker security”Run as non-root
Section titled “Run as non-root”The Pinchy Docker image runs as a non-root user by default. Verify this is not overridden in your docker-compose.yml:
services: pinchy: # Do NOT add: user: root security_opt: - no-new-privileges:trueRead-only filesystem
Section titled “Read-only filesystem”Mount the container filesystem as read-only and whitelist only the directories that need writes:
services: pinchy: read_only: true tmpfs: - /tmp volumes: - pinchy-data:/dataResource limits
Section titled “Resource limits”Prevent a single container from consuming all host resources:
services: pinchy: deploy: resources: limits: cpus: "2.0" memory: 2G reservations: memory: 512M db: deploy: resources: limits: cpus: "1.0" memory: 1GEnvironment variable protection
Section titled “Environment variable protection”The .env file contains secrets like DATABASE_URL, BETTER_AUTH_SECRET, and AUDIT_HMAC_SECRET. Protect it.
File permissions
Section titled “File permissions”chmod 600 .envchown root:root .envDocker Secrets (Swarm mode)
Section titled “Docker Secrets (Swarm mode)”If you use Docker Swarm, use secrets instead of environment variables:
services: pinchy: secrets: - db_password - auth_secret - audit_hmac_secret
secrets: db_password: file: ./secrets/db_password.txt auth_secret: file: ./secrets/auth_secret.txt audit_hmac_secret: file: ./secrets/audit_hmac_secret.txtNetwork isolation
Section titled “Network isolation”Pinchy’s Docker Compose setup uses an internal network. PostgreSQL and OpenClaw are not exposed to the host network.
Verify network isolation
Section titled “Verify network isolation”# Check that only Pinchy Web publishes portsdocker compose ps --format "table {{.Name}}\t{{.Ports}}"Only the pinchy service should show a published port (7777 by default). The db and openclaw services should show no published ports, or only 127.0.0.1:PORT bindings.
Custom Docker network
Section titled “Custom Docker network”For additional isolation, define an explicit internal network:
networks: pinchy-internal: internal: true pinchy-external:
services: pinchy: networks: - pinchy-internal - pinchy-external ports: - "127.0.0.1:7777:7777" db: networks: - pinchy-internal openclaw: networks: - pinchy-internalThe internal: true flag prevents containers on that network from reaching the internet. The pinchy service bridges both networks — it can reach the database and OpenClaw internally, while also serving HTTP traffic.
AUDIT_HMAC_SECRET configuration
Section titled “AUDIT_HMAC_SECRET configuration”The audit trail uses HMAC-SHA256 to sign every log entry. By default, Pinchy auto-generates a secret at startup. For production, set a persistent secret.
Generate a secret
Section titled “Generate a secret”openssl rand -hex 32Set it in your environment
Section titled “Set it in your environment”AUDIT_HMAC_SECRET=your-generated-hex-stringBackup & recovery
Section titled “Backup & recovery”PostgreSQL backups
Section titled “PostgreSQL backups”Create regular backups with pg_dump:
# Backupdocker compose exec db pg_dump -U pinchy pinchy | gzip > "pinchy-backup-$(date +%Y%m%d).sql.gz"
# Restoregunzip < pinchy-backup-20260222.sql.gz | docker compose exec -T db psql -U pinchy pinchyEncrypted backups
Section titled “Encrypted backups”Encrypt backups before storing them off-site:
# Backup + encrypt with GPGdocker compose exec db pg_dump -U pinchy pinchy | gzip | gpg --symmetric --cipher-algo AES256 > "pinchy-backup-$(date +%Y%m%d).sql.gz.gpg"
# Decrypt + restoregpg --decrypt pinchy-backup-20260222.sql.gz.gpg | gunzip | docker compose exec -T db psql -U pinchy pinchyBackup schedule
Section titled “Backup schedule”Set up a cron job for daily backups:
0 3 * * * root cd /opt/pinchy && docker compose exec -T db pg_dump -U pinchy pinchy | gzip | gpg --symmetric --batch --passphrase-file /root/.pinchy-backup-passphrase --cipher-algo AES256 > /backups/pinchy-$(date +\%Y\%m\%d).sql.gz.gpgUpdate strategy
Section titled “Update strategy”Docker image updates
Section titled “Docker image updates”Check for new Pinchy releases and update:
docker compose pulldocker compose up -dAlways read the release notes before updating. Back up the database first.
Dependency scanning
Section titled “Dependency scanning”Use tools like Trivy to scan your running images for known vulnerabilities:
trivy image ghcr.io/heypinchy/pinchy:latestConsider integrating this into a CI pipeline or running it on a schedule. See the SBOM reference for information about Pinchy’s software bill of materials.
Monitoring & alerting
Section titled “Monitoring & alerting”Health endpoint
Section titled “Health endpoint”Monitor the Pinchy Web service by polling its health endpoint:
curl -f https://pinchy.example.com/api/health || echo "Pinchy is down"Docker health checks
Section titled “Docker health checks”Add health checks to your docker-compose.yml:
services: pinchy: healthcheck: test: ["CMD", "curl", "-f", "http://localhost:7777/api/health"] interval: 30s timeout: 10s retries: 3 start_period: 30s db: healthcheck: test: ["CMD-SHELL", "pg_isready -U pinchy"] interval: 30s timeout: 5s retries: 3Log monitoring
Section titled “Log monitoring”Monitor Docker logs for errors and suspicious activity:
# Follow all service logsdocker compose logs -f
# Filter for errorsdocker compose logs pinchy 2>&1 | grep -i errorFor production, forward logs to a centralized logging system (e.g., Loki, Elasticsearch) and set up alerts for:
- Authentication failures (
auth.failedaudit events) - Denied tool executions (
tool.deniedaudit events) - Container restarts
- High error rates
Audit log monitoring
Section titled “Audit log monitoring”Use the audit trail API to monitor for security-relevant events programmatically:
# Check for failed logins in the last 24 hourscurl -b session_cookie "https://pinchy.example.com/api/audit?eventType=auth.failed&from=$(date -d '24 hours ago' -Iseconds)"See Audit Trail for the full API reference.