RippleCore
Infrastructure

Security Hardening

Advanced security measures for production environments

Security Hardening Guide

Advanced security measures for production environments

Target: Achieve defense-in-depth security posture with multiple layers of protection Compliance: GDPR-ready, SOC 2 preparation, industry best practices Prerequisites: Basic infrastructure deployed per DEPLOYMENT_CHECKLIST.md


Table of Contents


Security Baseline Assessment

Security Audit Checklist

Run Before Hardening:

#!/bin/bash
# security-audit.sh

echo "=========================================="
echo "Security Baseline Assessment"
echo "=========================================="

# Check SSH configuration
echo "✓ Checking SSH configuration..."
sshd -T | grep -E "passwordauthentication|permitrootlogin|pubkeyauthentication"

# Check firewall status
echo "✓ Checking firewall..."
ufw status verbose || echo "⚠️  UFW not installed"

# Check fail2ban
echo "✓ Checking fail2ban..."
fail2ban-client status || echo "⚠️  fail2ban not installed"

# Check for security updates
echo "✓ Checking security updates..."
apt list --upgradable 2>/dev/null | grep -i security

# Check open ports
echo "✓ Checking open ports..."
ss -tunlp

# Check Docker security
echo "✓ Checking Docker security..."
docker version
docker info | grep -i "security\|userns"

# Check SSL certificates
echo "✓ Checking SSL certificates..."
echo | openssl s_client -connect app.your-domain.com:443 2>/dev/null | openssl x509 -noout -dates

echo "=========================================="
echo "Audit Complete"
echo "=========================================="

Server Hardening

Operating System Hardening

1. Automatic Security Updates:

# Install unattended-upgrades
apt install unattended-upgrades apt-listchanges

# Configure automatic updates
cat > /etc/apt/apt.conf.d/50unattended-upgrades <<EOF
Unattended-Upgrade::Allowed-Origins {
    "\${distro_id}:\${distro_codename}-security";
    "\${distro_id}ESMApps:\${distro_codename}-apps-security";
    "\${distro_id}ESM:\${distro_codename}-infra-security";
};

Unattended-Upgrade::AutoFixInterruptedDpkg "true";
Unattended-Upgrade::MinimalSteps "true";
Unattended-Upgrade::Remove-Unused-Kernel-Packages "true";
Unattended-Upgrade::Remove-Unused-Dependencies "true";
Unattended-Upgrade::Automatic-Reboot "false";
Unattended-Upgrade::Automatic-Reboot-Time "03:00";
EOF

# Enable automatic updates
systemctl enable unattended-upgrades
systemctl start unattended-upgrades

2. Kernel Hardening (sysctl.conf):

# /etc/sysctl.d/99-security.conf
cat > /etc/sysctl.d/99-security.conf <<EOF
# IP Forwarding (disable if not routing)
net.ipv4.ip_forward = 0

# Ignore ICMP redirects
net.ipv4.conf.all.accept_redirects = 0
net.ipv6.conf.all.accept_redirects = 0

# Ignore source-routed packets
net.ipv4.conf.all.accept_source_route = 0
net.ipv6.conf.all.accept_source_route = 0

# Log martian packets
net.ipv4.conf.all.log_martians = 1

# Ignore ICMP pings (optional - can break monitoring)
# net.ipv4.icmp_echo_ignore_all = 1

# SYN flood protection
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_max_syn_backlog = 2048
net.ipv4.tcp_synack_retries = 2

# Increase ephemeral port range
net.ipv4.ip_local_port_range = 10000 65000

# TCP hardening
net.ipv4.tcp_rfc1337 = 1
net.ipv4.tcp_timestamps = 0

# Kernel randomization
kernel.randomize_va_space = 2

# Restrict kernel logs to root only
kernel.dmesg_restrict = 1

# Restrict access to kernel pointers
kernel.kptr_restrict = 2
EOF

# Apply settings
sysctl -p /etc/sysctl.d/99-security.conf

3. AppArmor Profiles (Ubuntu default):

# Verify AppArmor is running
aa-status

# Expected: Multiple Docker profiles in enforce mode

# Create custom profile for PostgreSQL container (optional)
cat > /etc/apparmor.d/docker-postgres <<EOF
#include <tunables/global>

profile docker-postgres flags=(attach_disconnected,mediate_deleted) {
  #include <abstractions/base>

  network inet tcp,
  network inet udp,
  network inet6 tcp,
  network inet6 udp,

  deny /proc/* r,
  deny /sys/** r,

  /var/lib/postgresql/data/** rw,
  /tmp/** rw,
}
EOF

# Load profile
apparmor_parser -r /etc/apparmor.d/docker-postgres

SSH Hardening (Beyond Basics)

Advanced SSH Configuration:

File: /etc/ssh/sshd_config

# Port (change from default 22 to reduce automated attacks)
Port 2222

# Protocol
Protocol 2

# Authentication
PermitRootLogin no
PasswordAuthentication no
PubkeyAuthentication yes
ChallengeResponseAuthentication no
UsePAM yes

# Key Exchange Algorithms (strong only)
KexAlgorithms curve25519-sha256@libssh.org,diffie-hellman-group-exchange-sha256

# Ciphers (strong only)
Ciphers chacha20-poly1305@openssh.com,aes256-gcm@openssh.com,aes128-gcm@openssh.com,aes256-ctr,aes192-ctr,aes128-ctr

# MACs (strong only)
MACs hmac-sha2-512-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512,hmac-sha2-256

# Limit login attempts
MaxAuthTries 3
MaxSessions 5

# Disconnect idle sessions (15 minutes)
ClientAliveInterval 300
ClientAliveCountMax 2

# Disable X11 forwarding
X11Forwarding no

# Disable TCP forwarding (if not needed)
AllowTcpForwarding no
AllowStreamLocalForwarding no

# Disable agent forwarding
AllowAgentForwarding no

# Only allow specific users/groups
AllowUsers deployer devops
# OR
AllowGroups ssh-users

# Log verbosely
LogLevel VERBOSE

# Use strong host keys only
HostKey /etc/ssh/ssh_host_ed25519_key
HostKey /etc/ssh/ssh_host_rsa_key

Restart SSH:

# IMPORTANT: Test new SSH connection BEFORE restarting
ssh -p 2222 root@server-ip

# If test succeeds, restart SSH
systemctl restart sshd

Update Firewall for New SSH Port:

# Hetzner Cloud Firewall (via Console)
# Change SSH rule: Port 22 → Port 2222

# UFW (if using)
ufw allow 2222/tcp comment 'SSH'
ufw delete allow 22/tcp

fail2ban Advanced Configuration

Enhanced fail2ban Setup:

File: /etc/fail2ban/jail.local

[DEFAULT]
# Ban for 1 hour
bantime = 3600

# Find time window: 10 minutes
findtime = 600

# Max retries before ban
maxretry = 3

# Ignore localhost and office IPs
ignoreip = 127.0.0.1/8 ::1 YOUR_OFFICE_IP/32

# Send email on ban (optional)
destemail = security@your-domain.com
sender = fail2ban@your-domain.com
action = %(action_mwl)s

[sshd]
enabled = true
port = 2222
logpath = /var/log/auth.log
maxretry = 3
bantime = 7200

[nginx-http-auth]
enabled = true
port = http,https
logpath = /var/log/nginx/error.log
maxretry = 5

[nginx-limit-req]
enabled = true
port = http,https
logpath = /var/log/nginx/error.log
maxretry = 10

[docker-auth]
enabled = true
port = 2375,2376
logpath = /var/log/docker.log
maxretry = 3
bantime = 86400

Restart fail2ban:

systemctl restart fail2ban

# Check status
fail2ban-client status

# View banned IPs
fail2ban-client status sshd

Docker Security

1. Docker Daemon Hardening:

File: /etc/docker/daemon.json

{
  "icc": false,
  "live-restore": true,
  "userland-proxy": false,
  "no-new-privileges": true,
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "default-ulimits": {
    "nofile": {
      "Name": "nofile",
      "Hard": 64000,
      "Soft": 64000
    }
  }
}

2. Run Containers as Non-Root:

# Dockerfile example
FROM node:20-alpine

# Create non-root user
RUN addgroup -g 1001 -S nodejs && \
    adduser -S nextjs -u 1001

# Switch to non-root user
USER nextjs

WORKDIR /app
COPY --chown=nextjs:nodejs . .

CMD ["node", "server.js"]

3. Docker Security Scanning (Trivy):

# Install Trivy
apt install wget apt-transport-https gnupg lsb-release
wget -qO - https://aquasecurity.github.io/trivy-repo/deb/public.key | apt-key add -
echo "deb https://aquasecurity.github.io/trivy-repo/deb $(lsb_release -sc) main" | tee -a /etc/apt/sources.list.d/trivy.list
apt update && apt install trivy

# Scan Docker images
trivy image ghcr.io/your-org/ripplecore-app:latest

# Scan for high/critical vulnerabilities only
trivy image --severity HIGH,CRITICAL ghcr.io/your-org/ripplecore-app:latest

# Auto-scan on image pull (in CI/CD)
# See CI_CD_PIPELINE.md for GitHub Actions integration

4. Resource Limits (prevent DoS):

# CPU limits
docker update --cpus="1.5" ripplecore-app

# Memory limits
docker update --memory="3g" --memory-reservation="2g" ripplecore-app

# Restart policies
docker update --restart=unless-stopped ripplecore-app

Network Security

Firewall Configuration (UFW)

Install and Configure UFW:

# Install UFW
apt install ufw

# Default policies
ufw default deny incoming
ufw default allow outgoing

# Allow SSH (use your custom port)
ufw allow 2222/tcp comment 'SSH'

# Allow HTTP/HTTPS
ufw allow 80/tcp comment 'HTTP'
ufw allow 443/tcp comment 'HTTPS'

# Allow Netdata (from office IP only)
ufw allow from YOUR_OFFICE_IP to any port 19999 comment 'Netdata'

# Enable UFW
ufw enable

# Verify status
ufw status verbose

Rate Limiting (UFW built-in):

# Rate limit SSH (max 6 connections per 30 seconds from same IP)
ufw limit 2222/tcp comment 'SSH rate limit'

# Rate limit HTTP/HTTPS
ufw limit 80/tcp comment 'HTTP rate limit'
ufw limit 443/tcp comment 'HTTPS rate limit'

Network Segmentation

Hetzner Cloud Private Network (already configured):

  • Production Network: 10.0.1.0/24

    • App Server: 10.0.1.2
    • DB Server: 10.0.1.3
    • CI/CD Server: 10.0.1.4
  • Staging Network: 10.0.2.0/24

    • Staging Server: 10.0.2.2

Database Isolation:

# Verify PostgreSQL only listens on private network
docker exec ripplecore-postgres psql -U ripplecore -c "SHOW listen_addresses;"
# Expected: 0.0.0.0 or 10.0.1.3

# Test from app server (should work)
psql -h 10.0.1.3 -U ripplecore -d ripplecore -c "SELECT 1"

# Test from internet (should fail - timeout)
psql -h PUBLIC_DB_IP -U ripplecore -d ripplecore -c "SELECT 1"
# Expected: Connection timeout (firewall blocks)

DDoS Protection

Cloudflare DDoS Protection (Free Plan):

  1. Enable Cloudflare (see PERFORMANCE_OPTIMIZATION.md)

  2. Firewall Rules (Block malicious traffic):

    Cloudflare Dashboard → Security → WAF
    
    Rules:
      - Block known bots (Challenge): Browser Integrity Check
      - Rate limiting: 100 requests/minute per IP
      - Block countries: (optional) Block high-risk countries
      - Challenge suspicious traffic: CAPTCHA for security_level = high
  3. Under Attack Mode (when under DDoS):

    Cloudflare Dashboard → Overview → Under Attack Mode: ON
    
    # Forces JavaScript challenge for all visitors
    # Use only during active attack

Arcjet Rate Limiting (already configured in app):

// Verify rate limits in packages/security/arcjet.ts
import arcjet, { shield, tokenBucket } from "@arcjet/next";

const aj = arcjet({
  key: process.env.ARCJET_KEY!,
  rules: [
    shield({ mode: "LIVE" }), // DDoS protection

    // Token bucket rate limiting
    tokenBucket({
      mode: "LIVE",
      refillRate: 10, // 10 tokens per interval
      interval: 10, // 10 seconds
      capacity: 20, // Burst capacity
    }),
  ],
});

Application Security

Content Security Policy (CSP)

Strict CSP Configuration:

File: packages/security/headers-config.ts

export const securityHeaders = [
  {
    key: "Content-Security-Policy",
    value: [
      "default-src 'self'",
      "script-src 'self' 'unsafe-eval' 'unsafe-inline' https://cdn.jsdelivr.net", // Remove unsafe-* in production
      "style-src 'self' 'unsafe-inline' https://fonts.googleapis.com",
      "font-src 'self' https://fonts.gstatic.com",
      "img-src 'self' data: https: blob:",
      "connect-src 'self' https://api.sentry.io https://*.arcjet.io",
      "frame-ancestors 'none'",
      "base-uri 'self'",
      "form-action 'self'",
      "upgrade-insecure-requests",
    ].join("; "),
  },
  {
    key: "X-Frame-Options",
    value: "DENY",
  },
  {
    key: "X-Content-Type-Options",
    value: "nosniff",
  },
  {
    key: "Referrer-Policy",
    value: "strict-origin-when-cross-origin",
  },
  {
    key: "Permissions-Policy",
    value: "camera=(), microphone=(), geolocation=(self), payment=()",
  },
  {
    key: "Strict-Transport-Security",
    value: "max-age=31536000; includeSubDomains; preload",
  },
];

Test CSP:

curl -I https://app.your-domain.com | grep -i "content-security-policy"

# Use Mozilla Observatory
# https://observatory.mozilla.org

Input Validation & Sanitization

Zod Validation (already used in packages):

// All API inputs MUST be validated
import { z } from "zod";

// ❌ BAD: No validation
export async function createKindness(data: any) {
  return db.insert(kindness).values(data);
}

// ✅ GOOD: Strict validation
const createKindnessSchema = z.object({
  companyId: z.string().uuid(),
  userId: z.string().uuid(),
  recipientName: z.string().min(1).max(100),
  message: z.string().min(1).max(1000),
  verificationLevel: z.number().int().min(1).max(5),
});

export async function createKindness(data: unknown) {
  // Throws if validation fails
  const validated = createKindnessSchema.parse(data);

  return db.insert(kindness).values(validated);
}

SQL Injection Prevention (Drizzle ORM):

// ✅ SAFE: Drizzle automatically parameterizes queries
const results = await db.query.users.findMany({
  where: eq(users.email, userInput), // Automatically escaped
});

// ❌ UNSAFE: Raw SQL (use only when necessary)
const results = await db.execute(
  sql`SELECT * FROM users WHERE email = ${userInput}`,
);
// Still safe with Drizzle's sql`` template, but avoid if possible

Authentication Security

better-auth Security Configuration:

File: packages/auth/server.ts

import { betterAuth } from "better-auth";

export const auth = betterAuth({
  database: {
    // ... database config
  },

  // Session security
  session: {
    expiresIn: 60 * 60 * 8, // 8 hours (PRD requirement)
    updateAge: 60 * 60, // Rotate session every 1 hour
    cookieCache: {
      enabled: true,
      maxAge: 5 * 60, // Cache session for 5 minutes
    },
  },

  // Strong password requirements
  password: {
    minLength: 12, // Minimum 12 characters
    requireNumbers: true,
    requireLowercase: true,
    requireUppercase: true,
    requireSpecialChars: true,
  },

  // Cookie security
  advanced: {
    useSecureCookies: process.env.NODE_ENV === "production",
    cookieSameSite: "lax",
    cookieSecure: true,
  },

  // Account lockout (prevent brute force)
  rateLimit: {
    enabled: true,
    window: 15 * 60, // 15 minutes
    max: 5, // Max 5 failed attempts
  },

  // Email verification required
  emailVerification: {
    required: true,
    expiresIn: 24 * 60 * 60, // 24 hours
  },

  // TOTP 2FA (optional but recommended)
  twoFactor: {
    enabled: true,
    issuer: "RippleCore",
  },
});

Password Hashing (Argon2 - automatic with better-auth):

// better-auth uses Argon2id by default (strongest algorithm)
// No manual hashing needed

// Verify password strength on client
import { zxcvbn } from "zxcvbn";

const passwordStrength = zxcvbn(password);
if (passwordStrength.score < 3) {
  throw new Error("Password too weak");
}

API Security

API Rate Limiting (per endpoint):

// apps/api/app/api/kindness/route.ts
import { rateLimitMiddleware } from "@repo/security/rate-limit";

export async function POST(request: Request) {
  // Apply rate limit: 10 requests per minute
  await rateLimitMiddleware(request, {
    limit: 10,
    window: 60,
  });

  // ... rest of handler
}

API Authentication (Bearer tokens):

// apps/api/middleware.ts
import { auth } from "@repo/auth/server";
import { headers } from "next/headers";

export async function authenticateAPI(request: Request) {
  const authHeader = request.headers.get("Authorization");

  if (!authHeader?.startsWith("Bearer ")) {
    throw new Error("Unauthorized");
  }

  const token = authHeader.substring(7);

  // Verify token with better-auth
  const session = await auth.api.getSession({
    headers: await headers(),
  });

  if (!session?.user) {
    throw new Error("Invalid token");
  }

  return session;
}

Database Security

PostgreSQL Security Hardening

1. Disable Remote Root Login:

-- Revoke superuser from remote users
REVOKE ALL ON DATABASE ripplecore FROM PUBLIC;

-- Create read-only user for reporting
CREATE USER ripplecore_readonly WITH PASSWORD 'strong-password';
GRANT CONNECT ON DATABASE ripplecore TO ripplecore_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO ripplecore_readonly;
ALTER DEFAULT PRIVILEGES IN SCHEMA public GRANT SELECT ON TABLES TO ripplecore_readonly;

2. Enable SSL/TLS:

# Generate self-signed certificate (or use Let's Encrypt)
openssl req -new -x509 -days 365 -nodes -text \
  -out /etc/postgresql/server.crt \
  -keyout /etc/postgresql/server.key \
  -subj "/CN=ripplecore-db"

chmod 600 /etc/postgresql/server.key
chown postgres:postgres /etc/postgresql/server.*

# Enable SSL in postgresql.conf
docker run -d \
  --name ripplecore-postgres \
  -e POSTGRES_SSL_MODE=require \
  -v /etc/postgresql/server.crt:/var/lib/postgresql/server.crt \
  -v /etc/postgresql/server.key:/var/lib/postgresql/server.key \
  postgres:18-alpine \
  -c ssl=on \
  -c ssl_cert_file=/var/lib/postgresql/server.crt \
  -c ssl_key_file=/var/lib/postgresql/server.key

Update Connection String:

DATABASE_URL=postgresql://ripplecore:<secret>@10.0.1.3:5432/ripplecore?sslmode=require

3. Audit Logging:

-- Enable query logging for security audit
ALTER SYSTEM SET log_statement = 'mod';  -- Log all modifications (INSERT, UPDATE, DELETE)
ALTER SYSTEM SET log_connections = on;
ALTER SYSTEM SET log_disconnections = on;
ALTER SYSTEM SET log_duration = on;
ALTER SYSTEM SET log_line_prefix = '%t [%p]: user=%u,db=%d,app=%a,client=%h ';

SELECT pg_reload_conf();

-- View logs
docker logs ripplecore-postgres --tail 100 | grep -E "INSERT|UPDATE|DELETE"

Redis Security

1. Authentication (requirepass):

# Generate strong password
REDIS_PASSWORD=$(openssl rand -base64 32)

docker run -d \
  --name ripplecore-redis \
  --restart unless-stopped \
  -p 6379:6379 \
  -v redis-data:/data \
  redis:7-alpine redis-server \
  --requirepass "$REDIS_PASSWORD" \
  --appendonly yes

# Update connection string
REDIS_URL=redis://:${REDIS_PASSWORD}@10.0.1.3:6379

2. Disable Dangerous Commands:

# Prevent FLUSHALL, FLUSHDB, CONFIG commands
docker run -d \
  --name ripplecore-redis \
  redis:7-alpine redis-server \
  --requirepass "$REDIS_PASSWORD" \
  --rename-command FLUSHDB "" \
  --rename-command FLUSHALL "" \
  --rename-command CONFIG "" \
  --rename-command SHUTDOWN SHUTDOWN_SECRET_COMMAND

3. Network Isolation (bind to private IP only):

docker run -d \
  --name ripplecore-redis \
  -p 10.0.1.3:6379:6379 \  # Bind to private IP only
  redis:7-alpine redis-server \
  --bind 10.0.1.3 \
  --protected-mode yes

Secrets Management

Environment Variable Security

1. Use 1Password CLI (for team secret management):

# Install 1Password CLI
curl -sS https://downloads.1password.com/linux/keys/1password.asc | gpg --dearmor --output /usr/share/keyrings/1password-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/1password-archive-keyring.gpg] https://downloads.1password.com/linux/debian/$(dpkg --print-architecture) stable main" | tee /etc/apt/sources.list.d/1password.list
apt update && apt install 1password-cli

# Authenticate
eval $(op signin)

# Load secrets from 1Password vault
export DATABASE_URL=$(op read "op://Production/Database/connection_string")
export REDIS_URL=$(op read "op://Production/Redis/connection_string")
export BETTER_AUTH_SECRET=$(op read "op://Production/Auth/secret")

2. Encrypt Secrets at Rest (Dokploy):

# Dokploy encrypts environment variables by default
# Verify encryption in Dokploy database
docker exec dokploy-db sqlite3 /app/data/dokploy.db \
  "SELECT key, substr(value, 1, 20) || '...' FROM environment_variables LIMIT 5;"
# Values should be encrypted (not readable)

3. Rotate Secrets Quarterly:

#!/bin/bash
# rotate-secrets.sh

# Generate new secrets
NEW_BETTER_AUTH_SECRET=$(npx @better-auth/cli secret)
NEW_REDIS_PASSWORD=$(openssl rand -base64 32)

# Update 1Password vault
op item edit "Production/Auth" secret="$NEW_BETTER_AUTH_SECRET"
op item edit "Production/Redis" password="$NEW_REDIS_PASSWORD"

# Update Dokploy environment variables
# (Manual step via Dokploy UI or API)

# Restart applications
docker restart ripplecore-app ripplecore-api ripplecore-web ripplecore-redis

echo "✅ Secrets rotated successfully"
echo "⚠️  Update backup scripts with new credentials"

Monitoring & Incident Response

Security Monitoring

1. Intrusion Detection (AIDE):

# Install AIDE
apt install aide

# Initialize database
aideinit

# Move database
mv /var/lib/aide/aide.db.new /var/lib/aide/aide.db

# Check for changes daily
cat > /etc/cron.daily/aide <<EOF
#!/bin/bash
/usr/bin/aide --check | mail -s "AIDE Report \$(hostname)" security@your-domain.com
EOF

chmod +x /etc/cron.daily/aide

2. File Integrity Monitoring:

# Monitor critical files
cat > /etc/aide/aide.conf.d/99_custom <<EOF
# Monitor SSH configuration
/etc/ssh/sshd_config$ p+i+n+u+g+s+b+m+c+md5+sha256

# Monitor system binaries
/usr/bin$ p+i+n+u+g+s+b+m+c+md5+sha256
/usr/sbin$ p+i+n+u+g+s+b+m+c+md5+sha256

# Monitor Docker configuration
/etc/docker/daemon.json$ p+i+n+u+g+s+b+m+c+md5+sha256
EOF

# Update AIDE database
aideinit

3. Log Monitoring (Logwatch):

# Install Logwatch
apt install logwatch

# Configure daily email reports
cat > /etc/cron.daily/00logwatch <<EOF
#!/bin/bash
/usr/sbin/logwatch --output mail --mailto security@your-domain.com --detail high
EOF

chmod +x /etc/cron.daily/00logwatch

Incident Response Plan

Incident Response Phases:

  1. Preparation (Proactive)

    • Security monitoring in place
    • Incident response team identified
    • Communication channels established
  2. Detection (Reactive)

    • Alerts from Netdata, UptimeRobot, Sentry
    • Log anomalies from fail2ban, Logwatch
    • User reports of suspicious activity
  3. Containment (Immediate)

    # Isolate compromised server
    # Via Hetzner Console → Server → Network → Disable
    
    # Block attacker IP
    ufw insert 1 deny from ATTACKER_IP
    
    # Revoke compromised credentials
    # Update secrets in 1Password and Dokploy
  4. Eradication (Investigation)

    • Identify attack vector
    • Remove malware/backdoors
    • Patch vulnerabilities
  5. Recovery (Restoration)

    • Restore from clean backup
    • Verify integrity
    • Resume normal operations
  6. Lessons Learned (Post-Incident)

    • Document incident
    • Update security measures
    • Train team on findings

Compliance & Auditing

GDPR Compliance

Data Protection Measures:

  • Data Encryption at Rest

    • PostgreSQL: Enable pgcrypto extension
    • Redis: AOF files on encrypted disk
    • Backups: GPG encrypted before S3 upload
  • Data Encryption in Transit

    • HTTPS enforced (HSTS preload)
    • PostgreSQL SSL/TLS required
    • Redis AUTH enabled
  • Data Minimization

    • Only collect necessary user data
    • Automatic deletion of old data (retention policies)
  • Right to Erasure

    • Implement user data export API
    • Implement user data deletion API
    • Cascade deletes in database schema
  • Breach Notification

    • 72-hour notification procedure
    • Incident response plan documented
    • Contact: dpo@your-domain.com

GDPR Checklist:

# Verify EU data residency
echo "Hetzner datacenter: Falkenstein, Germany (EU)"

# Verify encryption
curl -I https://app.your-domain.com | grep "Strict-Transport-Security"

# Verify data retention policies
docker exec ripplecore-postgres psql -U ripplecore -c "SELECT COUNT(*) FROM users WHERE created_at < NOW() - INTERVAL '2 years';"

Security Audit Logging

Enable Comprehensive Audit Logs:

-- PostgreSQL audit extension (pgAudit)
CREATE EXTENSION IF NOT EXISTS pgaudit;

-- Log all DDL and user actions
ALTER SYSTEM SET pgaudit.log = 'ddl, role, write';
ALTER SYSTEM SET pgaudit.log_catalog = off;
ALTER SYSTEM SET pgaudit.log_parameter = on;

SELECT pg_reload_conf();

-- Query audit logs
SELECT
  session_user_name,
  command,
  object_type,
  object_name,
  timestamp
FROM pgaudit.log
ORDER BY timestamp DESC
LIMIT 100;

Security Checklist Summary

Critical (Immediate Implementation)

  • SSH hardening (disable password auth, use keys only)
  • fail2ban installed and configured
  • Firewall enabled (UFW or Hetzner Cloud Firewall)
  • Automatic security updates enabled
  • PostgreSQL only accessible via private network
  • Redis authentication enabled
  • SSL/TLS on all services
  • Security headers configured
  • Secrets stored in 1Password (not Git)

Important (Within 1 Week)

  • AppArmor profiles for containers
  • Docker security scanning (Trivy)
  • Kernel hardening (sysctl)
  • Rate limiting (Arcjet + Cloudflare)
  • Input validation on all APIs (Zod)
  • Audit logging enabled
  • Incident response plan documented
  • Security monitoring (AIDE, Logwatch)
  • PostgreSQL SSL/TLS encryption
  • Encrypted backups (GPG)
  • 2FA for admin users
  • Security awareness training for team
  • Quarterly security audits
  • Penetration testing (external)
  • GDPR compliance review
  • SOC 2 preparation (if applicable)

Document Version: 1.0 Last Updated: 2025-01-23 Review Cycle: Quarterly or after security incidents Next Security Audit: [Schedule 3 months from now]