Skip to main content

Security Best Practices

Securing your self-hosted AFFiNE instance is crucial for protecting user data and maintaining trust. This guide covers essential security measures and best practices.

Network Security

HTTPS/TLS

Always use HTTPS in production:
1

Configure reverse proxy with TLS

Use Let’s Encrypt for free SSL certificates:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d affine.yourdomain.com
2

Update AFFiNE configuration

.env
AFFINE_SERVER_HTTPS=true
AFFINE_SERVER_HOST=affine.yourdomain.com
3

Enable HSTS

Add to Nginx configuration:
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
Never run AFFiNE in production without HTTPS. Credentials and data will be transmitted in plain text.

Firewall Configuration

Limit network access to essential ports:
# Allow SSH (change default port if possible)
sudo ufw allow 22/tcp

# Allow HTTP and HTTPS
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp

# Enable firewall
sudo ufw enable

# Verify rules
sudo ufw status

Network Isolation

Isolate services using Docker networks:
docker-compose.yml
networks:
  frontend:
    driver: bridge
  backend:
    driver: bridge
    internal: true

services:
  affine:
    networks:
      - frontend
      - backend
  
  postgres:
    networks:
      - backend
  
  redis:
    networks:
      - backend
This prevents direct access to database and Redis from outside the Docker network.

Authentication & Authorization

Strong Password Policy

Enforce strong passwords:
config.json
{
  "auth": {
    "passwordRequirements": {
      "min": 12,
      "max": 128
    }
  }
}
Consider requiring passwords with at least 12 characters including uppercase, lowercase, numbers, and symbols.

Disable Public Signup

For private deployments, disable public registration:
config.json
{
  "auth": {
    "allowSignup": false,
    "allowSignupForOauth": false
  }
}
Create accounts manually or via invitation only.

Email Verification

Require email verification before access:
config.json
{
  "auth": {
    "requireEmailVerification": true
  }
}

Session Security

Configure secure session settings:
config.json
{
  "auth": {
    "session": {
      "ttl": 604800,
      "ttr": 86400
    }
  }
}
  • ttl: Session lifetime (7 days)
  • ttr: Time to refresh (1 day)
Shorter TTL values improve security but reduce convenience.

OAuth Authentication

Use OAuth providers for centralized authentication:
{
  "oauth": {
    "providers": {
      "google": {
        "clientId": "your-client-id.apps.googleusercontent.com",
        "clientSecret": "your-client-secret"
      }
    }
  },
  "auth": {
    "requireEmailDomainVerification": true
  }
}

Database Security

Strong Database Password

Use a strong, unique password for PostgreSQL:
# Generate a secure password
openssl rand -base64 32
Set in .env:
.env
DB_PASSWORD=your_generated_secure_password_here

Network Access Control

Restrict PostgreSQL access:
docker-compose.yml
postgres:
  image: pgvector/pgvector:pg16
  networks:
    - backend  # Not exposed to frontend network
  # Don't expose ports to host
  # ports:
  #   - "5432:5432"  # NEVER do this in production

Encryption at Rest

For sensitive data, use encrypted volumes:
# Create encrypted volume using LUKS
sudo cryptsetup luksFormat /dev/sdb
sudo cryptsetup luksOpen /dev/sdb affine_encrypted
sudo mkfs.ext4 /dev/mapper/affine_encrypted
sudo mount /dev/mapper/affine_encrypted /var/lib/affine

Regular Backups

Encrypt backups before storing:
# Backup and encrypt
docker compose exec postgres pg_dump -U affine affine | \
  gzip | \
  gpg --encrypt --recipient admin@yourdomain.com \
  > affine-backup-$(date +%Y%m%d).sql.gz.gpg

Database User Permissions

Use least privilege principle:
-- Create read-only user for reporting
CREATE USER affine_readonly WITH PASSWORD 'secure_password';
GRANT CONNECT ON DATABASE affine TO affine_readonly;
GRANT USAGE ON SCHEMA public TO affine_readonly;
GRANT SELECT ON ALL TABLES IN SCHEMA public TO affine_readonly;

Application Security

Private Key Security

The private key is used for signing tokens and encrypting sensitive data:
1

Generate secure key

openssl genrsa -out private.key 4096
2

Store securely

chmod 600 private.key
sudo chown root:root private.key
3

Set in environment

.env
AFFINE_PRIVATE_KEY=$(cat private.key)
4

Backup the key

Store encrypted backup in secure location (password manager, vault, etc.).
If the private key is compromised, all sessions and encrypted data may be at risk. Rotate immediately if compromise is suspected.

Rate Limiting

Protect against brute force and DoS attacks:
config.json
{
  "throttle": {
    "enabled": true,
    "throttlers": {
      "default": {
        "ttl": 60,
        "limit": 60
      },
      "strict": {
        "ttl": 60,
        "limit": 10
      }
    }
  }
}
For additional protection, use Nginx rate limiting:
http {
    limit_req_zone $binary_remote_addr zone=affine_limit:10m rate=10r/s;
    
    server {
        location / {
            limit_req zone=affine_limit burst=20 nodelay;
            proxy_pass http://localhost:3010;
        }
    }
}

CAPTCHA Protection

Enable CAPTCHA for authentication:
config.json
{
  "captcha": {
    "enabled": true,
    "config": {
      "turnstile": {
        "secret": "your-cloudflare-turnstile-secret"
      },
      "challenge": {
        "bits": 20
      }
    }
  }
}

Content Security Policy

Add CSP headers in Nginx:
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self' data:; connect-src 'self' wss:; frame-ancestors 'none';" always;

Security Headers

Add comprehensive security headers:
add_header X-Frame-Options "SAMEORIGIN" always;
add_header X-Content-Type-Options "nosniff" always;
add_header X-XSS-Protection "1; mode=block" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()" always;

Storage Security

File Upload Restrictions

Set maximum upload size:
client_max_body_size 100M;
Configure in WebSocket settings:
config.json
{
  "websocket": {
    "maxHttpBufferSize": 100000000
  }
}

S3 Security

When using S3-compatible storage:
{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Principal": {
        "AWS": "arn:aws:iam::ACCOUNT:user/affine"
      },
      "Action": [
        "s3:GetObject",
        "s3:PutObject",
        "s3:DeleteObject"
      ],
      "Resource": "arn:aws:s3:::your-bucket/*"
    }
  ]
}
Enable server-side encryption:
config.json
{
  "storages": {
    "blob": {
      "storage": {
        "provider": "aws-s3",
        "config": {
          "serverSideEncryption": "AES256"
        }
      }
    }
  }
}
Use IAM roles instead of access keys when possible:
{
  "storages": {
    "blob": {
      "storage": {
        "provider": "aws-s3",
        "config": {
          "credentials": {
            "fromInstanceMetadata": true
          }
        }
      }
    }
  }
}

Monitoring & Auditing

Enable Audit Logging

Monitor security-relevant events:
# Monitor authentication attempts
docker compose logs -f affine | grep -i "auth\|login\|password"

# Monitor failed access attempts
docker compose logs -f affine | grep -i "unauthorized\|forbidden\|denied"

Intrusion Detection

Install and configure Fail2Ban:
sudo apt install fail2ban

# Create filter for AFFiNE
sudo cat > /etc/fail2ban/filter.d/affine.conf << EOF
[Definition]
failregex = ^.*Authentication failed.*<HOST>.*$
ignoreregex =
EOF

# Create jail
sudo cat > /etc/fail2ban/jail.d/affine.conf << EOF
[affine]
enabled = true
port = http,https
filter = affine
logpath = /var/log/affine/access.log
maxretry = 5
bantime = 3600
EOF

sudo systemctl restart fail2ban

Security Scanning

Regularly scan for vulnerabilities:
# Scan Docker images
docker scout cves ghcr.io/toeverything/affine:stable

# Update base images
docker compose pull
docker compose up -d

Compliance & Privacy

Data Privacy

  • Implement data export functionality
  • Provide account deletion with data removal
  • Document data processing activities
  • Implement data retention policies
  • Enable encryption at rest and in transit
Ensure data stays in required regions:
config.json
{
  "storages": {
    "blob": {
      "storage": {
        "provider": "aws-s3",
        "config": {
          "region": "eu-central-1"
        }
      }
    }
  }
}

Access Control

Implement role-based access control:
  • Regular users: Standard workspace access
  • Workspace admins: Manage workspace members
  • System admins: Server configuration access
Document who has access to:
  • Server infrastructure
  • Database backups
  • Encryption keys
  • Configuration files

Incident Response

Security Incident Plan

1

Detect

  • Monitor logs and metrics
  • Set up alerts for suspicious activity
  • Regular security audits
2

Contain

  • Isolate affected systems
  • Block malicious IPs
  • Disable compromised accounts
3

Investigate

  • Collect logs and evidence
  • Identify attack vector
  • Assess impact and data exposure
4

Remediate

  • Patch vulnerabilities
  • Rotate credentials
  • Update security controls
5

Recover

  • Restore from clean backups
  • Verify system integrity
  • Resume normal operations
6

Review

  • Document incident
  • Update procedures
  • Notify affected parties if required

Compromised Credentials

If credentials are compromised:
# 1. Rotate database password
docker compose exec postgres psql -U postgres -c "ALTER USER affine PASSWORD 'new_secure_password';"

# 2. Update environment
vim .env  # Update DB_PASSWORD

# 3. Rotate private key
mv ~/.affine/self-host/config/private.key ~/.affine/self-host/config/private.key.old
openssl genrsa -out ~/.affine/self-host/config/private.key 4096

# 4. Restart services
docker compose down
docker compose up -d

# 5. Force logout all users (sessions invalidated by key rotation)

Security Checklist

Before going to production:
1

Network Security

  • HTTPS enabled with valid certificate
  • Firewall configured
  • Services isolated on internal network
  • Unnecessary ports closed
2

Authentication

  • Strong password policy enforced
  • Email verification enabled
  • Public signup disabled (if required)
  • OAuth configured (optional)
  • Session timeouts configured
3

Database

  • Strong database password set
  • Database not exposed to public
  • Backups enabled and tested
  • Encryption at rest (optional)
4

Application

  • Private key generated and secured
  • Rate limiting enabled
  • Security headers configured
  • File upload limits set
5

Monitoring

  • Logging enabled
  • Monitoring configured
  • Alerts set up
  • Incident response plan documented
6

Maintenance

  • Update procedure documented
  • Security patch process established
  • Regular security reviews scheduled

Security Resources

Security is an ongoing process, not a one-time setup. Regularly review and update your security measures.

Next Steps