gohorsejobs/docs/BACKEND_SECURITY.md
Tiago Yamamoto fd9af24e22 docs: update documentation with new features and security analysis
- Add BACKEND_SECURITY.md with security analysis and hardening guide
- Add FRONTEND_TESTING_STRATEGY.md with test coverage strategy
- Update API.md with new endpoints (candidates, tickets, credentials)
- Update AGENTS.md documentation index
2026-02-25 05:51:06 -06:00

327 lines
8.6 KiB
Markdown
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 🔒 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.