# Security Audit Report **Date:** October 15, 2025 **Auditor:** Cascade AI **Scope:** Full application security review --- ## Executive Summary This comprehensive security audit examined authentication, authorization, input validation, data protection, and API security across the fotbal-club application. The application demonstrates **good security practices** in most areas, but several **critical and high-priority vulnerabilities** require immediate attention. **Overall Security Rating:** ⚠️ **MEDIUM-HIGH RISK** --- ## 🔴 Critical Findings ### 1. **Exposed API Keys and Credentials in .env File** **Severity:** CRITICAL **Location:** `.env` (lines 84, 96) **Issue:** - Real OpenRouter API key exposed: `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` - Real Umami Analytics credentials exposed: `admin / eevRQ6h3G@!c#y4A1T` **Impact:** - Unauthorized API usage and potential billing charges - Complete compromise of analytics account - These credentials are committed to version control **Recommendation:** ```bash # IMMEDIATE ACTION REQUIRED: # 1. Rotate all exposed credentials NOW # 2. Remove from .env and use .env.example for templates # 3. Add .env to .gitignore # 4. Review git history and consider repository secrets scanning # 5. Use environment-specific secrets management (Azure Key Vault, AWS Secrets Manager, etc.) ``` ### 2. **Weak Default JWT Secret** **Severity:** CRITICAL **Location:** `pkg/utils/jwt.go` (line 67), `.env` (line 20) **Issue:** ```go func GetJWTSecret() string { secret := os.Getenv("JWT_SECRET") if secret == "" { return "default-secret-key-change-in-production" // ⚠️ Weak default } return secret } ``` Current `.env` also uses weak secret: `your_jwt_secret_key_here` **Impact:** - JWT tokens can be forged if default is used in production - Complete authentication bypass possible - All user sessions compromised **Recommendation:** ```go // Fail hard if JWT secret is missing or weak func GetJWTSecret() string { secret := os.Getenv("JWT_SECRET") if secret == "" || secret == "default-secret-key-change-in-production" || secret == "your_jwt_secret_key_here" || len(secret) < 32 { log.Fatal("SECURITY: JWT_SECRET must be set to a strong secret (minimum 32 characters)") } return secret } ``` **Note:** `main.go` (line 184) checks this for production only - should check ALL environments. --- ## 🟠 High-Priority Findings ### 3. **Dev Bypass Middleware Allows Admin Access Without Authentication** **Severity:** HIGH **Location:** `internal/middleware/auth.go` (lines 78-92) **Issue:** ```go func DevBypass() gin.HandlerFunc { return func(c *gin.Context) { if config.AppConfig != nil && config.AppConfig.AppEnv != "production" { if strings.ToLower(c.GetHeader("X-Dev-Admin")) == "true" { c.Set("userRole", "admin") c.Set("user", &models.User{Role: "admin"}) // ⚠️ Fake admin user c.Next() return } } c.Next() } } ``` **Impact:** - Any request with `X-Dev-Admin: true` header gets admin access in non-production - No authentication required - Development/staging environments fully compromised **Recommendation:** ```go // Remove DevBypass entirely or restrict to localhost only func DevBypass() gin.HandlerFunc { return func(c *gin.Context) { // Only allow from localhost if config.AppConfig != nil && config.AppConfig.AppEnv == "development" { if c.ClientIP() == "127.0.0.1" || c.ClientIP() == "::1" { if strings.ToLower(c.GetHeader("X-Dev-Admin")) == "true" { log.Println("WARNING: DevBypass used from localhost") c.Set("userRole", "admin") c.Set("user", &models.User{Role: "admin"}) c.Next() return } } } c.Next() } } ``` ### 4. **Admin Access Token Bypass** **Severity:** HIGH **Location:** `internal/middleware/auth.go` (lines 18-27) **Issue:** ```go if config.AppConfig != nil && config.AppConfig.AdminAccessToken != "" { header := c.GetHeader("X-Admin-Token") if header != "" && header == config.AppConfig.AdminAccessToken { c.Set("userRole", "admin") c.Set("user", &models.User{Role: "admin"}) c.Next() return } } ``` **Impact:** - Bypasses normal JWT authentication - Single token compromise = full admin access - No rate limiting on this check - Token doesn't expire **Recommendation:** - Remove this feature entirely OR - Implement proper token rotation and expiration - Add rate limiting - Log all usage - Restrict to specific IP ranges ### 5. **Insufficient Input Sanitization** **Severity:** HIGH **Location:** `pkg/utils/sanitize.go` **Issue:** - Custom regex-based HTML sanitization is prone to bypasses - No use of industry-standard sanitization library (bluemonday) - Comment on line 9 acknowledges: "For production, consider using bluemonday library" **Impact:** - XSS vulnerabilities possible - HTML injection attacks - JavaScript execution in user-controlled content **Recommendation:** ```bash # Install bluemonday go get github.com/microcosm-cc/bluemonday # Replace regex-based sanitization with bluemonday ``` ```go import "github.com/microcosm-cc/bluemonday" func SanitizeHTML(html string) string { p := bluemonday.UGCPolicy() // User-generated content policy return p.Sanitize(html) } ``` ### 6. **Frontend XSS Risk - dangerouslySetInnerHTML Usage** **Severity:** HIGH **Location:** Multiple frontend files **Affected Files:** - `frontend/src/pages/AboutPage.tsx` (4 occurrences) - `frontend/src/pages/ArticleDetailPage.tsx` (1 occurrence) - `frontend/src/pages/ActivityDetailPage.tsx` (1 occurrence - using DOMPurify ✓) - `frontend/src/pages/ClubPage.tsx` (1 occurrence) - `frontend/src/pages/admin/NewsletterAdminPage.tsx` (2 occurrences - using sanitizeHtml ✓) **Issue:** Some uses sanitize properly, others don't consistently: ```tsx // ⚠️ Direct use without sanitization // ✅ Proper sanitization ``` **Recommendation:** - Ensure ALL `dangerouslySetInnerHTML` uses are sanitized with DOMPurify - Create a wrapper component to enforce sanitization --- ## 🟡 Medium-Priority Findings ### 7. **CSRF Protection Gaps** **Severity:** MEDIUM **Location:** `internal/middleware/csrf.go` **Issues:** 1. **In-memory token storage** (line 19): Tokens lost on server restart 2. **No CSRF on Bearer token requests** (line 62): Skips CSRF if Bearer header present 3. **1-hour token expiry**: May be too short for long forms **Recommendation:** ```go // Use Redis or database for token persistence // Consider requiring CSRF for sensitive operations even with Bearer tokens // Extend token lifetime to 4-8 hours for better UX ``` ### 8. **Rate Limiting Not Applied to All Endpoints** **Severity:** MEDIUM **Location:** `internal/routes/routes.go` **Issue:** Rate limiting only on specific endpoints: - Login: 15/minute - Register: 5/hour - File upload: 30/minute - Contact form: 10/minute - Newsletter: 30/minute - Poll voting: 10/minute Missing rate limiting on: - Password reset verification - Admin API endpoints - File operations (delete, scan) - Newsletter subscriber management **Recommendation:** Apply global rate limiting middleware with per-endpoint overrides. ### 9. **SQL Injection - Low Risk (GORM Protected)** **Severity:** LOW (Informational) **Location:** Throughout controllers **Finding:** Application uses GORM ORM properly with parameterized queries. No raw SQL detected in controllers. Examples: ```go // ✅ Safe - parameterized db.Where("email = ?", email).First(&user) db.Where("LOWER(email) = LOWER(?)", email).First(&user) ``` **Status:** ✅ **PASS** - No SQL injection vulnerabilities detected ### 10. **Password Policy** **Severity:** MEDIUM **Location:** `internal/controllers/auth_controller.go` (line 21) **Issue:** ```go Password string `json:"password" binding:"required,min=8"` ``` Minimum 8 characters, but no complexity requirements. **Recommendation:** ```go // Add password validation func ValidatePassword(password string) error { if len(password) < 12 { return errors.New("password must be at least 12 characters") } // Check for uppercase, lowercase, number, special char hasUpper := regexp.MustCompile(`[A-Z]`).MatchString(password) hasLower := regexp.MustCompile(`[a-z]`).MatchString(password) hasNumber := regexp.MustCompile(`[0-9]`).MatchString(password) hasSpecial := regexp.MustCompile(`[!@#$%^&*(),.?":{}|<>]`).MatchString(password) if !(hasUpper && hasLower && hasNumber && hasSpecial) { return errors.New("password must contain uppercase, lowercase, number, and special character") } return nil } ``` ### 11. **File Upload Security** **Severity:** MEDIUM **Location:** `internal/config/config.go`, `internal/controllers/files_controller.go` **Findings:** ✅ **Good practices:** - File size limits (10MB) - MIME type whitelist - Filename sanitization (`pkg/utils/sanitize.go`) ⚠️ **Issues:** - No virus scanning - SVG files allowed (can contain scripts) - File content not validated (MIME type spoofing possible) **Recommendation:** ```go // 1. Remove SVG from allowed types or sanitize thoroughly // 2. Validate file content, not just extension // 3. Store uploads outside webroot // 4. Consider ClamAV integration for virus scanning ``` ### 12. **Session Management** **Severity:** MEDIUM **Location:** JWT implementation **Issues:** - JWT tokens are stateless - no revocation mechanism - 24-hour expiration (reasonable but fixed) - No refresh token mechanism - HttpOnly cookies used (✅ good) but no rotation **Recommendation:** Implement token blacklist or use short-lived access tokens with refresh tokens. --- ## 🟢 Security Strengths The application demonstrates several **excellent security practices**: 1. ✅ **Password Hashing:** Uses bcrypt with proper cost (`pkg/utils/password.go`) 2. ✅ **HttpOnly Cookies:** Prevents XSS token theft (`internal/controllers/auth_controller.go` line 150-158) 3. ✅ **Secure Cookie Flags:** Sets Secure flag when using HTTPS 4. ✅ **CORS Configuration:** Properly configured with origin validation (`main.go` line 111-130) 5. ✅ **Security Headers:** X-Content-Type-Options, X-Frame-Options, HSTS (`main.go` line 99-105) 6. ✅ **Content Security Policy:** Configurable CSP header 7. ✅ **Admin Password Verification:** Required when modifying admin accounts (`internal/controllers/auth_controller.go` line 491) 8. ✅ **Email Case-Insensitive Lookup:** Prevents duplicate accounts with different casing 9. ✅ **Graceful Shutdown:** Proper cleanup on server shutdown (`main.go`) 10. ✅ **Database Connection Pooling:** Configured limits prevent resource exhaustion --- ## Priority Action Items ### Immediate (Within 24 hours): 1. **Rotate all exposed credentials** in `.env` file 2. **Remove .env from version control**, use .env.example 3. **Set strong JWT_SECRET** (minimum 32 characters, random) 4. **Disable or restrict DevBypass middleware** ### Short-term (Within 1 week): 5. **Replace custom HTML sanitization** with bluemonday 6. **Audit all dangerouslySetInnerHTML** usage in frontend 7. **Remove or secure Admin Access Token** feature 8. **Implement comprehensive rate limiting** ### Medium-term (Within 1 month): 9. **Add password complexity requirements** 10. **Implement JWT token blacklist** or refresh tokens 11. **Add file content validation** for uploads 12. **Implement persistent CSRF token storage** --- ## Compliance Notes ### GDPR Considerations: - ✅ Newsletter unsubscribe mechanism present - ✅ Email preferences management - ⚠️ Consider adding data export/deletion endpoints ### OWASP Top 10 (2021): - **A01 Broken Access Control:** ⚠️ DevBypass and AdminAccessToken issues - **A02 Cryptographic Failures:** ⚠️ Weak default JWT secret - **A03 Injection:** ✅ GORM protects against SQL injection - **A04 Insecure Design:** ⚠️ In-memory CSRF tokens - **A05 Security Misconfiguration:** ⚠️ Exposed credentials - **A07 Identification/Authentication Failures:** ⚠️ Weak password policy - **A08 Software/Data Integrity:** ⚠️ No subresource integrity checks --- ## Testing Recommendations 1. **Penetration Testing:** Engage security professionals for thorough testing 2. **Dependency Scanning:** Use `go mod` security scanning tools 3. **SAST/DAST:** Implement automated security scanning in CI/CD 4. **Secret Scanning:** Use tools like GitGuardian or Gitleaks --- ## Conclusion The fotbal-club application has a **solid security foundation** with proper use of bcrypt, GORM ORM, security headers, and CORS. However, **critical vulnerabilities** exist around exposed credentials, weak JWT defaults, and authentication bypasses that must be addressed immediately. **Recommended next steps:** 1. Fix all CRITICAL issues within 24 hours 2. Schedule HIGH priority fixes within 1 week 3. Implement continuous security monitoring 4. Conduct regular security audits --- **Report End** For questions or clarifications, review the specific file locations and code references provided above.