# 🔒 Backend Security Analysis - GoHorseJobs > **Last Updated:** 2026-02-25 > **Analyzed by:** Security Review > **Target:** Go Backend (Go 1.24, net/http, PostgreSQL) --- ## 📊 Stack Tecnológica Identificada | Componente | Tecnologia | Status | |------------|------------|--------| | **Framework/Roteador** | `net/http` nativo | ✅ | | **Banco de Dados** | PostgreSQL + `lib/pq` (SQL nativo) | ✅ | | **Autenticação** | JWT HS256 + Cookies HttpOnly | ⚠️ Requer atenção | | **Validação** | Manual (sem biblioteca estruturada) | ⚠️ Requer atenção | | **Secrets** | `os.Getenv()` | ✅ | | **Logging** | `fmt.Println` / `log.Printf` | ⚠️ Não estruturado | | **Criptografia** | RSA-OAEP + bcrypt + AES-256-GCM | ✅ | --- ## 🚨 Vulnerabilidades Identificadas ### 1. JWT Secret Hardcoded (CRÍTICO) **Arquivo:** `internal/utils/jwt.go:10` ```go var jwtSecret = []byte("your-secret-key-change-this-in-production") // TODO: Move to env var ``` **Risco:** Secret hardcoded no código-fonte. Se o repositório for exposto, todos os tokens podem ser forjados. **Status:** ❌ Não corrigido **Correção Recomendada:** ```go var jwtSecret = []byte(os.Getenv("JWT_SECRET")) func init() { if len(jwtSecret) < 32 { panic("JWT_SECRET must be at least 32 characters") } } ``` --- ### 2. Logging de Dados Sensíveis (ALTO) **Arquivo:** `internal/api/middleware/auth_middleware.go` ```go fmt.Printf("[AUTH DEBUG] Token from Header (first 20 chars): '%s...'\n", token[:min(20, len(token))]) fmt.Printf("[AUTH DEBUG] Token VALID! Claims: sub=%v, tenant=%v, roles=%v\n", ...) ``` **Risco:** Tokens JWT e claims logados em produção. Pode vazar informações sensíveis. **Status:** ❌ Não corrigido **Correção Recomendada:** ```go import "log/slog" // Usar níveis de log slog.Debug("Token validated", "sub", claims["sub"], "roles", claims["roles"]) // Em produção, nunca logar tokens ``` --- ### 3. Falta de JTI para Revogação (MÉDIO) O JWT não inclui `jti` (JWT ID), impossibilitando revogação individual de tokens. **Status:** ❌ Não implementado --- ## 📋 Guia de Hardening ### 1️⃣ Prevenção de SQL Injection **Status Atual:** ✅ Mitigado O código usa placeholders corretamente: ```go query := `SELECT id, full_name, email FROM users WHERE role = $1 LIMIT $2` rows, err := s.DB.QueryContext(ctx, query, role, limit) ``` **Boas Práticas:** - ❌ NUNCA usar `fmt.Sprintf` para construir queries - ✅ Sempre usar `$1, $2, ...` placeholders - ✅ Validar input antes de passar para queries **Implementação Recomendada:** ```go // Wrapper para validação de queries func validateQuery(query string) error { dangerousPatterns := []string{ `'; DROP`, `'; DELETE`, `'; UPDATE`, `'; INSERT`, `OR 1=1`, `OR '1'='1`, `UNION SELECT`, } lower := strings.ToLower(query) for _, pattern := range dangerousPatterns { if strings.Contains(lower, strings.ToLower(pattern)) { return fmt.Errorf("potential SQL injection detected") } } return nil } ``` --- ### 2️⃣ Segurança em Goroutines **Código Atual:** ```go // internal/services/application_service.go:61 go func() { // Processamento assíncrono sem recover }() // internal/api/handlers/seeder_handler.go:45 go func() { // Seeder em background sem context }() ``` **Riscos:** 1. **Goroutine Leak**: Goroutines presas consomem memória 2. **Race Condition**: Acesso concorrente a recursos compartilhados 3. **Panic não tratado**: Crash do servidor **Implementação Segura:** ```go func (s *Service) ProcessAsync(ctx context.Context, data Data) error { g, ctx := errgroup.WithContext(ctx) g.Go(func() error { defer func() { if r := recover(); r != nil { slog.Error("Panic in goroutine", "error", r) } }() select { case <-ctx.Done(): return ctx.Err() default: return s.doWork(data) } }) return g.Wait() } // Rate limiting para goroutines type GoroutinePool struct { sem chan struct{} } func NewPool(max int) *GoroutinePool { return &GoroutinePool{sem: make(chan struct{}, max)} } func (p *GoroutinePool) Go(fn func()) { p.sem <- struct{}{} go func() { defer func() { <-p.sem }() fn() }() } ``` --- ### 3️⃣ Validação de JWT - Hardening **Implementação Recomendada:** ```go package auth import ( "crypto/subtle" "time" "github.com/golang-jwt/jwt/v5" "github.com/google/uuid" ) type SecureClaims struct { UserID string `json:"sub"` TenantID string `json:"tenant,omitempty"` Roles []string `json:"roles"` TokenID string `json:"jti"` // Para revogação TokenType string `json:"type"` // "access" ou "refresh" jwt.RegisteredClaims } func (s *JWTService) ValidateToken(tokenString string) (*SecureClaims, error) { token, err := jwt.ParseWithClaims(tokenString, &SecureClaims{}, func(token *jwt.Token) (interface{}, error) { // ✅ Verificar algoritmo explicitamente if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { return nil, fmt.Errorf("unexpected signing method: %v", token.Header["alg"]) } return s.config.SecretKey, nil }, jwt.WithValidMethods([]string{"HS256"}), jwt.WithIssuer(s.config.Issuer), jwt.WithAudience(s.config.Audience...), ) if err != nil { return nil, err } claims, ok := token.Claims.(*SecureClaims) if !ok || !token.Valid { return nil, errors.New("invalid token") } // ✅ Verificar se foi revogado (Redis lookup) if s.isRevoked(claims.TokenID) { return nil, errors.New("token revoked") } return claims, nil } ``` --- ### 4️⃣ Proteção Contra Replay e CSRF **CSRF (implementação recomendada):** ```go func (h *Handler) WithCSRF(next http.Handler) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { if r.Method != "GET" && r.Method != "HEAD" && r.Method != "OPTIONS" { csrfToken := r.Header.Get("X-CSRF-Token") cookieToken, err := r.Cookie("csrf_token") if err != nil || csrfToken == "" || cookieToken.Value == "" { http.Error(w, "CSRF token missing", http.StatusForbidden) return } if subtle.ConstantTimeCompare([]byte(csrfToken), []byte(cookieToken.Value)) != 1 { http.Error(w, "CSRF token invalid", http.StatusForbidden) return } } next.ServeHTTP(w, r) }) } ``` **Replay Attack Protection:** ```go type TokenStore struct { redis *redis.Client } func (s *TokenStore) IsReplayed(jti string, exp time.Time) bool { key := fmt.Sprintf("used_token:%s", jti) // Verificar se já foi usado if s.redis.Exists(context.Background(), key).Val() > 0 { return true // Replay detectado } // Marcar como usado até a expiração ttl := time.Until(exp) if ttl > 0 { s.redis.Set(context.Background(), key, "1", ttl) } return false } ``` --- ## 📊 Resumo de Riscos | Vulnerabilidade | Severidade | Status | |-----------------|------------|--------| | JWT Secret Hardcoded | **CRÍTICO** | ❌ Pendente | | Logging de Tokens | **ALTO** | ❌ Pendente | | Falta de JTI/Revogação | **MÉDIO** | ❌ Pendente | | Goroutine Leak | **MÉDIO** | ❌ Pendente | | Falta de CSRF Token | **MÉDIO** | ❌ Pendente | | SQL Injection | **BAIXO** | ✅ Mitigado | --- ## 🛠️ Plano de Ação Prioritário | Prioridade | Ação | Esforço | |------------|------|---------| | **P0** | Mover `jwtSecret` hardcoded para `JWT_SECRET` env var | 1h | | **P0** | Remover logs de tokens em produção | 30min | | **P1** | Implementar logging estruturado (slog/zap) | 4h | | **P1** | Adicionar `jti` aos tokens e implementar revogação | 8h | | **P2** | Implementar CSRF tokens para operações sensíveis | 4h | | **P2** | Adicionar middleware de rate limiting | 4h | | **P3** | Migrar para RS256 (JWT assimétrico) para rotação de chaves | 16h | --- ## 📚 Referências - [OWASP Go Security Cheat Sheet](https://cheatsheetseries.owasp.org/cheatsheets/Go_Security_Cheat_Sheet.html) - [JWT Best Practices](https://auth0.com/blog/jwt-authentication-best-practices/) - [Go Concurrency Patterns](https://go.dev/blog/pipelines) - [CWE-798: Hard-coded Credentials](https://cwe.mitre.org/data/definitions/798.html) --- > **Nota:** Este documento deve ser revisado trimestralmente ou após mudanças significativas na arquitetura de segurança.