16 KiB
Security Best Practices
This document outlines security best practices for developing and maintaining Charon. These guidelines help prevent common vulnerabilities and ensure compliance with industry standards.
Table of Contents
- Secret Management
- Logging Security
- Input Validation
- File System Security
- Database Security
- API Security
- Compliance
- Security Testing
Secret Management
Principles
- Never commit secrets to version control
- Use environment variables for production
- Rotate secrets regularly
- Mask secrets in logs
- Encrypt secrets at rest
API Keys and Tokens
Storage
- Development: Store in
.envfile (gitignored) - Production: Use environment variables or secret management service
- File storage: Use 0600 permissions (owner read/write only)
# Example: Secure key file creation
echo "api-key-here" > /data/crowdsec/bouncer.key
chmod 0600 /data/crowdsec/bouncer.key
chown charon:charon /data/crowdsec/bouncer.key
Masking
Always mask secrets before logging:
// ✅ GOOD: Masked secret
logger.Infof("API Key: %s", maskAPIKey(apiKey))
// ❌ BAD: Full secret exposed
logger.Infof("API Key: %s", apiKey)
Charon's masking rules:
- Empty:
[empty] - Short (< 16 chars):
[REDACTED] - Normal (≥ 16 chars):
abcd...xyz9(first 4 + last 4)
Validation
Validate secret format before use:
if !validateAPIKeyFormat(apiKey) {
return fmt.Errorf("invalid API key format")
}
Requirements:
- Length: 16-128 characters
- Charset: Alphanumeric + underscore + hyphen
- No spaces or special characters
Rotation
Rotate secrets regularly:
- Schedule: Every 90 days (recommended)
- Triggers: After suspected compromise, employee offboarding, security incidents
- Process:
- Generate new secret
- Update configuration
- Test with new secret
- Revoke old secret
- Update documentation
Passwords and Credentials
- Storage: Hash with bcrypt (cost factor ≥ 12) or Argon2
- Transmission: HTTPS only
- Never log: Full passwords or password hashes
- Requirements: Enforce minimum complexity and length
Logging Security
What to Log
✅ Safe to log:
- Timestamps
- User IDs (not usernames if PII)
- IP addresses (consider GDPR implications)
- Request paths (sanitize query parameters)
- Response status codes
- Error types (generic messages)
- Performance metrics
❌ Never log:
- Passwords or password hashes
- API keys or tokens (use masking)
- Session IDs (full values)
- Credit card numbers
- Social security numbers
- Personal health information (PHI)
- Any Personally Identifiable Information (PII)
Log Sanitization
Before logging user input, sanitize:
// ✅ GOOD: Sanitized logging
logger.Infof("Login attempt from IP: %s", sanitizeIP(ip))
// ❌ BAD: Direct user input
logger.Infof("Login attempt: username=%s password=%s", username, password)
Log Retention
- Development: 7 days
- Production: 30-90 days (depends on compliance requirements)
- Audit logs: 1-7 years (depends on regulations)
Important: Shorter retention reduces exposure risk if logs are compromised.
Log Aggregation
If using external log services (CloudWatch, Splunk, Datadog):
- Ensure logs are encrypted in transit (TLS)
- Ensure logs are encrypted at rest
- Redact sensitive data before shipping
- Apply same retention policies
- Audit access controls regularly
Input Validation
Principles
- Validate all inputs (user-provided, file uploads, API requests)
- Whitelist approach: Define what's allowed, reject everything else
- Fail securely: Reject invalid input with generic error messages
- Sanitize before use: Escape/encode for target context
File Uploads
// ✅ GOOD: Comprehensive validation
func validateUpload(file multipart.File, header *multipart.FileHeader) error {
// 1. Check file size
if header.Size > maxFileSize {
return fmt.Errorf("file too large")
}
// 2. Validate file type (magic bytes, not extension)
buf := make([]byte, 512)
file.Read(buf)
mimeType := http.DetectContentType(buf)
if !isAllowedMimeType(mimeType) {
return fmt.Errorf("invalid file type")
}
// 3. Sanitize filename
safeName := sanitizeFilename(header.Filename)
// 4. Check for path traversal
if containsPathTraversal(safeName) {
return fmt.Errorf("invalid filename")
}
return nil
}
Path Traversal Prevention
// ✅ GOOD: Secure path handling
func securePath(baseDir, userPath string) (string, error) {
// Clean and resolve path
fullPath := filepath.Join(baseDir, filepath.Clean(userPath))
// Ensure result is within baseDir
if !strings.HasPrefix(fullPath, baseDir) {
return "", fmt.Errorf("path traversal detected")
}
return fullPath, nil
}
// ❌ BAD: Direct path join (vulnerable)
fullPath := baseDir + "/" + userPath
SQL Injection Prevention
// ✅ GOOD: Parameterized query
db.Where("email = ?", email).First(&user)
// ❌ BAD: String concatenation (vulnerable)
db.Raw("SELECT * FROM users WHERE email = '" + email + "'").Scan(&user)
Command Injection Prevention
// ✅ GOOD: Use exec.Command with separate arguments
cmd := exec.Command("cscli", "bouncers", "list")
// ❌ BAD: Shell with user input (vulnerable)
cmd := exec.Command("sh", "-c", "cscli bouncers list " + userInput)
File System Security
File Permissions
| File Type | Permissions | Owner | Rationale |
|---|---|---|---|
| Secret files (keys, tokens) | 0600 | charon:charon | Owner read/write only |
| Configuration files | 0640 | charon:charon | Owner read/write, group read |
| Log files | 0640 | charon:charon | Owner read/write, group read |
| Executables | 0750 | root:charon | Owner read/write/execute, group read/execute |
| Data directories | 0750 | charon:charon | Owner full access, group read/execute |
Directory Structure
/data/charon/
├── config/ (0750 charon:charon)
│ ├── config.yaml (0640 charon:charon)
│ └── secrets/ (0700 charon:charon) - Secret storage
│ └── api.key (0600 charon:charon)
├── logs/ (0750 charon:charon)
│ └── app.log (0640 charon:charon)
└── data/ (0750 charon:charon)
Temporary Files
// ✅ GOOD: Secure temp file creation
f, err := os.CreateTemp("", "charon-*.tmp")
if err != nil {
return err
}
defer os.Remove(f.Name()) // Clean up
// Set secure permissions
if err := os.Chmod(f.Name(), 0600); err != nil {
return err
}
Database Security
Query Security
- Always use parameterized queries (GORM
Wherewith?placeholders) - Validate all inputs before database operations
- Use transactions for multi-step operations
- Limit query results (avoid SELECT *)
- Index sensitive columns sparingly (balance security vs performance)
Sensitive Data
| Data Type | Storage Method | Example |
|---|---|---|
| Passwords | bcrypt hash | bcrypt.GenerateFromPassword([]byte(password), 12) |
| API Keys | Environment variable or encrypted field | os.Getenv("API_KEY") |
| Tokens | Hashed with random salt | sha256(token + salt) |
| PII | Encrypted at rest | AES-256-GCM |
Migrations
// ✅ GOOD: Add sensitive field with proper constraints
migrator.AutoMigrate(&User{})
// ❌ BAD: Store sensitive data in plaintext
// (Don't add columns like `password_plaintext`)
API Security
Authentication
- Use JWT tokens or session cookies with secure flags
- Implement rate limiting (prevent brute force)
- Enforce HTTPS in production
- Validate all tokens before processing requests
Authorization
// ✅ GOOD: Check user permissions
if !user.HasPermission("crowdsec:manage") {
return c.JSON(403, gin.H{"error": "forbidden"})
}
// ❌ BAD: Assume user has access
// (No permission check)
Rate Limiting
Protect endpoints from abuse:
// Example: 100 requests per hour per IP
limiter := rate.NewLimiter(rate.Every(36*time.Second), 100)
Critical endpoints (require stricter limits):
- Login: 5 attempts per 15 minutes
- Password reset: 3 attempts per hour
- API key generation: 5 per day
Input Validation
// ✅ GOOD: Validate request body
type CreateBouncerRequest struct {
Name string `json:"name" binding:"required,min=3,max=64,alphanum"`
}
if err := c.ShouldBindJSON(&req); err != nil {
return c.JSON(400, gin.H{"error": "invalid request"})
}
Error Handling
// ✅ GOOD: Generic error message
return c.JSON(401, gin.H{"error": "authentication failed"})
// ❌ BAD: Reveals authentication details
return c.JSON(401, gin.H{"error": "invalid API key: abc123"})
Compliance
GDPR (General Data Protection Regulation)
Applicable if: Processing data of EU residents
Requirements:
- Data minimization: Collect only necessary data
- Purpose limitation: Use data only for stated purposes
- Storage limitation: Delete data when no longer needed
- Security: Implement appropriate technical measures (encryption, masking)
- Breach notification: Report breaches within 72 hours
Implementation:
- ✅ Charon masks API keys in logs (prevents exposure of personal data)
- ✅ Secure file permissions (0600) protect sensitive data
- ✅ Log retention policies prevent indefinite storage
- ⚠️ Ensure API keys don't contain personal identifiers
Reference: GDPR Article 32 - Security of processing
PCI-DSS (Payment Card Industry Data Security Standard)
Applicable if: Processing, storing, or transmitting credit card data
Requirements:
- Requirement 3.4: Render PAN unreadable (encryption, masking)
- Requirement 8.2: Strong authentication
- Requirement 10.2: Audit trails
- Requirement 10.7: Retain audit logs for 1 year
Implementation:
- ✅ Charon uses masking for sensitive credentials (same principle for PAN)
- ✅ Secure file permissions align with access control requirements
- ⚠️ Charon doesn't handle payment cards directly (delegated to payment processors)
Reference: PCI-DSS Quick Reference Guide
SOC 2 (System and Organization Controls)
Applicable if: SaaS providers, cloud services
Trust Service Criteria:
- CC6.1: Logical access controls (authentication, authorization)
- CC6.6: Encryption of data in transit
- CC6.7: Encryption of data at rest
- CC7.2: Monitoring and detection (logging, alerting)
Implementation:
- ✅ API key validation ensures strong credentials (CC6.1)
- ✅ File permissions (0600) protect data at rest (CC6.7)
- ✅ Masked logging enables monitoring without exposing secrets (CC7.2)
- ⚠️ Ensure HTTPS enforcement for data in transit (CC6.6)
Reference: SOC 2 Trust Services Criteria
ISO 27001 (Information Security Management)
Applicable to: Any organization implementing ISMS
Key Controls:
- A.9.4.3: Password management systems
- A.10.1.1: Cryptographic controls
- A.12.4.1: Event logging
- A.18.1.5: Protection of personal data
Implementation:
- ✅ API key format validation (minimum 16 chars, charset restrictions)
- ✅ Key rotation procedures documented
- ✅ Secure storage with file permissions (0600)
- ✅ Masked logging protects sensitive data
Reference: ISO 27001:2013 Controls
Compliance Summary Table
| Framework | Key Requirement | Charon Implementation | Status |
|---|---|---|---|
| GDPR | Data protection (Art. 32) | API key masking, secure storage | ✅ Compliant |
| PCI-DSS | Render PAN unreadable (Req. 3.4) | Masking utility (same principle) | ✅ Aligned |
| SOC 2 | Logical access controls (CC6.1) | Key validation, file permissions | ✅ Compliant |
| ISO 27001 | Password management (A.9.4.3) | Key rotation, validation | ✅ Compliant |
Security Testing
Static Analysis
# Run CodeQL security scan
.github/skills/scripts/skill-runner.sh security-codeql-scan
# Expected: 0 CWE-312/315/359 findings
Unit Tests
# Run security-focused unit tests
go test ./backend/internal/api/handlers -run TestMaskAPIKey -v
go test ./backend/internal/api/handlers -run TestValidateAPIKeyFormat -v
go test ./backend/internal/api/handlers -run TestSaveKeyToFile_SecurePermissions -v
Integration Tests
# Run Playwright E2E tests
.github/skills/scripts/skill-runner.sh test-e2e-playwright
# Check for exposed secrets in test logs
grep -i "api[_-]key\|token\|password" playwright-report/index.html
# Expected: Only masked values (abcd...xyz9) or no matches
Penetration Testing
Recommended schedule: Annual or after major releases
Focus areas:
- Authentication bypass
- Authorization vulnerabilities
- SQL injection
- Path traversal
- Information disclosure (logs, errors)
- Rate limiting effectiveness
Security Checklist
Before Every Release
- Run CodeQL scan (0 critical findings)
- Run unit tests (100% pass)
- Run integration tests (100% pass)
- Check for hardcoded secrets (TruffleHog, Semgrep)
- Review log output for sensitive data exposure
- Verify file permissions (secrets: 0600, configs: 0640)
- Update dependencies (no known CVEs)
- Review security documentation updates
- Test secret rotation procedure
- Verify HTTPS enforcement in production
During Code Review
- No secrets in environment variables (use .env)
- All secrets are masked in logs
- Input validation on all user-provided data
- Parameterized queries (no string concatenation)
- Secure file permissions (0600 for secrets)
- Error messages don't reveal sensitive info
- No commented-out secrets or debugging code
- Security tests added for new features
After Security Incident
- Rotate all affected secrets immediately
- Audit access logs for unauthorized use
- Purge logs containing exposed secrets
- Notify affected users (if PII exposed)
- Update incident response procedures
- Document lessons learned
- Implement additional controls to prevent recurrence
Resources
Internal Documentation
External References
- OWASP Top 10
- OWASP Cheat Sheet Series
- CWE Top 25
- NIST Cybersecurity Framework
- SANS Top 25 Software Errors
Security Standards
Updates
| Date | Change | Author |
|---|---|---|
| 2026-02-03 | Initial security practices documentation | GitHub Copilot |
Last Updated: 2026-02-03 Next Review: 2026-05-03 (Quarterly) Owner: Security Team / Lead Developer