# Regla: Seguridad Go

Esta regla es OBLIGATORIA para todo código Go de producción. Go tiene defaults
seguros en muchas áreas, pero no en todas. Las vulnerabilidades documentadas
aquí son evitables con los patrones correctos desde el inicio.

---

## gosec como scanner de seguridad en CI

`gosec` analiza el AST de Go buscando patrones de código inseguro. Es el primer
mecanismo de defensa automática.

```bash
# Instalar
go install github.com/securego/gosec/v2/cmd/gosec@latest

# Ejecutar sobre todo el proyecto
gosec ./...

# En CI — falla si hay issues de severidad alta o media
gosec -severity medium -confidence medium -fmt json -out gosec-report.json ./...
```

Reglas críticas que gosec verifica:
- G101: Credenciales hardcodeadas
- G201/G202: SQL injection
- G304: Path traversal en lectura de archivos
- G401/G501: Uso de algoritmos criptográficos débiles (MD5, SHA1)
- G501: Import de `math/rand` para uso criptográfico

---

## No usar unsafe sin justificación en ADR

El paquete `unsafe` rompe las garantías de tipo y memoria de Go. Es legítimo
en casos muy específicos (interop con C, serialización de alto rendimiento),
pero nunca sin documentación explícita.

```go
// MAL — unsafe sin justificación
import "unsafe"
func convertirBytes(b []byte) string {
    return *(*string)(unsafe.Pointer(&b))
}

// BIEN — conversión segura (copia, pero es lo correcto)
func convertirBytes(b []byte) string {
    return string(b)
}

// Excepción documentada — si se requiere unsafe por rendimiento crítico:
// 1. Crear ADR documentando la justificación
// 2. Aislar en archivo con nombre: unsafe_*.go
// 3. Comentario inline explicando por qué es seguro en este caso específico
```

---

## crypto/rand para secretos — nunca math/rand

`math/rand` es un PRNG determinista. Predecible. Jamás para tokens, IDs de sesión,
claves criptográficas ni cualquier valor que deba ser impredecible.

```go
// MAL — math/rand: predecible, no apto para seguridad
import "math/rand"
token := fmt.Sprintf("%d", rand.Int63())  // VULNERABLE

// BIEN — crypto/rand: criptográficamente seguro
import (
    "crypto/rand"
    "encoding/base64"
)

func GenerarToken(n int) (string, error) {
    bytes := make([]byte, n)
    if _, err := rand.Read(bytes); err != nil {
        return "", fmt.Errorf("generar token: %w", err)
    }
    return base64.URLEncoding.EncodeToString(bytes), nil
}
```

---

## html/template para HTML — no text/template

`text/template` NO escapa HTML automáticamente. Usar `html/template` siempre
que se genere HTML para evitar XSS.

```go
// MAL — text/template: sin escape automático de HTML
import "text/template"
tmpl := template.Must(template.New("").Parse("<h1>{{.Nombre}}</h1>"))

// BIEN — html/template: escapa automáticamente contenido potencialmente peligroso
import "html/template"
tmpl := template.Must(html_template.New("").Parse("<h1>{{.Nombre}}</h1>"))
// Si .Nombre = "<script>alert('xss')</script>", se renderiza como texto seguro
```

---

## SQL con placeholders — nunca fmt.Sprintf

La inyección SQL es tan simple de evitar como usar placeholders. No hay excusa.

```go
// MAL — SQL injection garantizada
query := fmt.Sprintf("SELECT * FROM facturas WHERE cliente_id = '%s'", clienteID)
rows, err := db.QueryContext(ctx, query)

// MAL — strings.Builder no ayuda
var sb strings.Builder
sb.WriteString("SELECT * FROM facturas WHERE cliente_id = '")
sb.WriteString(clienteID)

// BIEN — placeholders: la BD separa código de datos
rows, err := db.QueryContext(ctx,
    "SELECT id, total, estatus FROM facturas WHERE cliente_id = $1 AND estatus = $2",
    clienteID, estatus,
)

// BIEN — con sqlx o GORM (internamente usan placeholders)
var facturas []Factura
err := db.SelectContext(ctx, &facturas,
    "SELECT * FROM facturas WHERE cliente_id = $1", clienteID)
```

---

## TLS 1.2+ en clientes HTTP

El cliente HTTP por defecto de Go es seguro en versiones recientes, pero
es mejor ser explícito con la configuración mínima de TLS.

```go
// MAL — skip de verificación de certificado (nunca en producción)
cliente := &http.Client{
    Transport: &http.Transport{
        TLSClientConfig: &tls.Config{InsecureSkipVerify: true},  // PROHIBIDO
    },
}

// BIEN — TLS mínimo explícito
cliente := &http.Client{
    Timeout: 30 * time.Second,
    Transport: &http.Transport{
        TLSClientConfig: &tls.Config{
            MinVersion: tls.VersionTLS12,
        },
        DialContext: (&net.Dialer{
            Timeout:   10 * time.Second,
            KeepAlive: 30 * time.Second,
        }).DialContext,
    },
}
```

---

## No almacenar secretos en structs serializables

Los structs con tags `json:` o `yaml:` pueden serializarse accidentalmente
en logs, respuestas de API o archivos de configuración.

```go
// MAL — el campo Password aparece en JSON si se serializa Config
type Config struct {
    Host     string `json:"host"`
    Password string `json:"password"`  // PELIGROSO: aparece en logs/respuestas
    APIKey   string `json:"api_key"`
}

// BIEN — secretos sin tag json, o con "-" para excluirlos siempre
type Config struct {
    Host     string `json:"host"`
    Password string `json:"-"`   // nunca serializado
    APIKey   string `json:"-"`
}

// BIEN — tipo opaco que no serializa su contenido
type SecretString string
func (s SecretString) MarshalJSON() ([]byte, error) {
    return []byte(`"[REDACTED]"`), nil
}
```

---

## Rate limiting con golang.org/x/time/rate

```go
import "golang.org/x/time/rate"

// Limiter: 10 requests por segundo, burst de 20
limiter := rate.NewLimiter(rate.Limit(10), 20)

// Middleware de rate limiting
func RateLimitMiddleware(limiter *rate.Limiter) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            if !limiter.Allow() {
                http.Error(w, "Demasiadas solicitudes", http.StatusTooManyRequests)
                return
            }
            next.ServeHTTP(w, r)
        })
    }
}
```

---

## Checklist de seguridad Go antes de abrir PR

- [ ] `gosec ./...` pasa sin issues de severidad media o alta
- [ ] Sin importación de `math/rand` para valores de seguridad — usar `crypto/rand`
- [ ] Sin `InsecureSkipVerify: true` en ningún cliente TLS
- [ ] SQL usa placeholders (`$1`, `?`) — sin fmt.Sprintf en queries
- [ ] HTML renderizado con `html/template`, no `text/template`
- [ ] Sin `unsafe` sin ADR documentado y comentario de justificación
- [ ] Secretos en structs marcados con `json:"-"` o tipo opaco
- [ ] Rate limiting configurado en endpoints públicos
