From fd9af24e220cccc51f2b77577986129b092c7075 Mon Sep 17 00:00:00 2001 From: Tiago Yamamoto Date: Wed, 25 Feb 2026 05:51:06 -0600 Subject: [PATCH] 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 --- docs/AGENTS.md | 3 + docs/API.md | 18 +++ docs/BACKEND_SECURITY.md | 327 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 348 insertions(+) create mode 100644 docs/BACKEND_SECURITY.md diff --git a/docs/AGENTS.md b/docs/AGENTS.md index e8a99bd..77a9da6 100644 --- a/docs/AGENTS.md +++ b/docs/AGENTS.md @@ -98,6 +98,9 @@ When asked to learn or modify parts of the system, consult these files first: * [API.md](API.md) - Endpoint contracts * [API_SECURITY.md](API_SECURITY.md) - Auth, RBAC * [APPSEC_STRATEGY.md](APPSEC_STRATEGY.md) - Application Security and Testing +* [BACKEND_SECURITY.md](BACKEND_SECURITY.md) - Backend Security Analysis & Hardening * [DATABASE.md](DATABASE.md) - Schema and ERD * [DEVOPS.md](DEVOPS.md) - Cloudflare, Traefik, Containers +* [FRONTEND_TESTING_STRATEGY.md](FRONTEND_TESTING_STRATEGY.md) - Frontend Test Coverage Strategy * [TASKS.md](TASKS.md) / [TEST_USERS.md](TEST_USERS.md) + diff --git a/docs/API.md b/docs/API.md index 60cf761..8ab8b3d 100644 --- a/docs/API.md +++ b/docs/API.md @@ -46,6 +46,24 @@ View detailed interactive Swagger Docs by visiting `/swagger/index.html`. * `GET /applications`: Lists received applications (for recruiters) or submitted applications (for candidates). * `POST /applications/{id}/status`: Updates application status (`reviewing`, `accepted`, `rejected`). +### Candidates (Admin) +* `GET /candidates`: Lists all candidates with pagination (`?page=1&perPage=10`). Returns stats and pagination metadata. + +### Tickets & Support (Backend) +* `POST /support/tickets`: Creates a support ticket. Requires `{ subject, category, priority, message }`. Categories: `bug`, `feature`, `support`, `billing`, `other`. +* `GET /support/tickets`: Lists user's tickets. +* `GET /support/tickets/{id}`: Get ticket details with messages. +* `POST /support/tickets/{id}/messages`: Add message to ticket. +* `POST /support/tickets/{id}/close`: Close ticket. + +### Settings & Credentials +* `GET /settings/{key}`: Get settings by key (e.g., `theme`). +* `POST /settings/{key}`: Save settings. +* `GET /credentials`: List configured external services. +* `POST /credentials/{service}`: Save credentials for a service. +* `DELETE /credentials/{service}`: Delete credentials. +* **Supported Services:** `stripe`, `storage` (S3), `cloudflare_config`, `cpanel`, `lavinmq`, `appwrite`, `fcm_service_account`, `smtp`. + ### Miscellaneous * `GET /health`: Basic ping endpoint. * `POST /storage/upload-url`: Requests a presigned URL (S3/Cloudflare R2) to upload logos or resumes directly from the browser. diff --git a/docs/BACKEND_SECURITY.md b/docs/BACKEND_SECURITY.md new file mode 100644 index 0000000..131dff3 --- /dev/null +++ b/docs/BACKEND_SECURITY.md @@ -0,0 +1,327 @@ +# 🔒 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.