# Golang Security

> When to activate: Go security, JWT, bcrypt, CORS, input validation, SQL injection prevention, secrets management, rate limiting in Go

- Skill: `mattakushi432/golang-security` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/golang-security`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/golang-security/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Data & Analytics
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/golang-security

---


# Go Security Patterns

## Password Hashing (bcrypt)

```go
import "golang.org/x/crypto/bcrypt"

func HashPassword(password string) (string, error) {
    hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
    return string(hash), err
}

func CheckPassword(password, hash string) bool {
    return bcrypt.CompareHashAndPassword([]byte(hash), []byte(password)) == nil
}
```

## JWT Authentication

```go
import "github.com/golang-jwt/jwt/v5"

type Claims struct {
    UserID string `json:"sub"`
    Role   string `json:"role"`
    jwt.RegisteredClaims
}

func GenerateToken(userID, role, secret string) (string, error) {
    claims := Claims{
        UserID: userID,
        Role:   role,
        RegisteredClaims: jwt.RegisteredClaims{
            ExpiresAt: jwt.NewNumericDate(time.Now().Add(24 * time.Hour)),
            IssuedAt:  jwt.NewNumericDate(time.Now()),
            Issuer:    "myapp",
        },
    }
    return jwt.NewWithClaims(jwt.SigningMethodHS256, claims).
        SignedString([]byte(secret))
}

func ValidateToken(tokenStr, secret string) (*Claims, error) {
    token, err := jwt.ParseWithClaims(tokenStr, &Claims{}, func(t *jwt.Token) (any, error) {
        if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
            return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
        }
        return []byte(secret), nil
    })
    if err != nil { return nil, err }
    if claims, ok := token.Claims.(*Claims); ok && token.Valid {
        return claims, nil
    }
    return nil, errors.New("invalid token")
}
```

## Input Validation

```go
import "github.com/go-playground/validator/v10"

var validate = validator.New()

type CreateArticleRequest struct {
    Title   string   `json:"title"   validate:"required,min=3,max=200"`
    Body    string   `json:"body"    validate:"required,min=10"`
    Tags    []string `json:"tags"    validate:"max=10,dive,alphanum"`
}

func validateRequest(req any) error {
    if err := validate.Struct(req); err != nil {
        var ve validator.ValidationErrors
        if errors.As(err, &ve) {
            msgs := make([]string, len(ve))
            for i, fe := range ve {
                msgs[i] = fmt.Sprintf("%s: %s", fe.Field(), fe.Tag())
            }
            return fmt.Errorf("validation failed: %s", strings.Join(msgs, ", "))
        }
        return err
    }
    return nil
}
```

## SQL Injection Prevention

```go
// ALWAYS use parameterized queries — never string concatenation
// Bad
db.Exec("SELECT * FROM users WHERE email = '" + email + "'")

// Good — database/sql
row := db.QueryRowContext(ctx, "SELECT id, name FROM users WHERE email = $1", email)

// Good — GORM
db.Where("email = ?", email).First(&user)
db.Where("email = ? AND role = ?", email, role).Find(&users)
```

## Secrets from Environment

```go
import "github.com/caarlos0/env/v11"

type Config struct {
    DBUrl     string `env:"DATABASE_URL,required"`
    JWTSecret string `env:"JWT_SECRET,required"`
    Port      int    `env:"PORT" envDefault:"8080"`
}

func LoadConfig() (Config, error) {
    var cfg Config
    return cfg, env.Parse(&cfg)
}

// Never hardcode secrets; validate at startup
func main() {
    cfg, err := LoadConfig()
    if err != nil { log.Fatalf("config error: %v", err) }
    // ...
}
```

## CORS Configuration

```go
// Gin
r.Use(cors.New(cors.Config{
    AllowOrigins:     []string{"https://app.example.com"},
    AllowMethods:     []string{"GET", "POST", "PUT", "DELETE"},
    AllowHeaders:     []string{"Authorization", "Content-Type"},
    ExposeHeaders:    []string{"X-Request-ID"},
    AllowCredentials: true,
    MaxAge:           12 * time.Hour,
}))

// net/http manually
func corsMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Access-Control-Allow-Origin", "https://app.example.com")
        w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
        w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type")
        if r.Method == http.MethodOptions {
            w.WriteHeader(http.StatusNoContent)
            return
        }
        next.ServeHTTP(w, r)
    })
}
```

## Rate Limiting

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

type IPLimiter struct {
    limiters sync.Map
    rate     rate.Limit
    burst    int
}

func (l *IPLimiter) Get(ip string) *rate.Limiter {
    v, _ := l.limiters.LoadOrStore(ip, rate.NewLimiter(l.rate, l.burst))
    return v.(*rate.Limiter)
}

func rateLimitMiddleware(limiter *IPLimiter) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            ip, _, _ := net.SplitHostPort(r.RemoteAddr)
            if !limiter.Get(ip).Allow() {
                http.Error(w, "Too Many Requests", http.StatusTooManyRequests)
                return
            }
            next.ServeHTTP(w, r)
        })
    }
}
```

## Common Anti-Patterns

- **`md5`/`sha1` for passwords** — always use bcrypt, scrypt, or argon2id
- **Logging request bodies with credentials** — strip `Authorization`, `Cookie`, `password` fields before logging
- **`AllowAllOrigins: true` in CORS** — whitelist specific origins in production
- **Embedding secrets in binary** — use environment variables or a secret manager
- **`gorilla/sessions` with default key** — generate a cryptographically random session key at startup

