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
This commit is contained in:
Tiago Yamamoto 2026-02-25 05:51:06 -06:00
parent a1ee608611
commit fd9af24e22
3 changed files with 348 additions and 0 deletions

View file

@ -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)

View file

@ -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.

327
docs/BACKEND_SECURITY.md Normal file
View file

@ -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.