- Added clarity and structure to README files, including recent updates and getting started sections. - Improved manual verification documentation for CrowdSec authentication, emphasizing expected outputs and success criteria. - Updated debugging guide with detailed output examples and automatic trace capture information. - Refined best practices for E2E tests, focusing on efficient polling, locator strategies, and state management. - Documented triage report for DNS Provider feature tests, highlighting issues fixed and test results before and after improvements. - Revised E2E test writing guide to include when to use specific helper functions and patterns for better test reliability. - Enhanced troubleshooting documentation with clear resolutions for common issues, including timeout and token configuration problems. - Updated tests README to provide quick links and best practices for writing robust tests.
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