security
Installation
SKILL.md
Security Skill
Security guidelines based on OWASP, Trail of Bits, and industry best practices.
When to Use This Skill
- Implementing authentication/authorization
- Validating and sanitizing user input
- Reviewing code for security vulnerabilities
- Configuring secure deployments
- Handling sensitive data
🔐 Authentication
Password Handling
import "golang.org/x/crypto/bcrypt"
// Hash password before storing
func hashPassword(password string) (string, error) {
bytes, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
return string(bytes), err
}
// Verify password
func checkPassword(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
JWT Token Handling
import "github.com/golang-jwt/jwt/v5"
type Claims struct {
UserID string `json:"user_id"`
Role string `json:"role"`
jwt.RegisteredClaims
}
func generateToken(userID, role string, secret []byte) (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: "llmproxy",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(secret)
}
func validateToken(tokenString string, secret []byte) (*Claims, error) {
token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(t *jwt.Token) (interface{}, error) {
if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method: %v", t.Header["alg"])
}
return secret, nil
})
if err != nil {
return nil, err
}
if claims, ok := token.Claims.(*Claims); ok && token.Valid {
return claims, nil
}
return nil, fmt.Errorf("invalid token")
}
Secure Session Management
- Use secure, httpOnly cookies
- Implement session timeout
- Regenerate session ID after login
- Invalidate session on logout
http.SetCookie(w, &http.Cookie{
Name: "session",
Value: sessionToken,
HttpOnly: true, // Not accessible via JavaScript
Secure: true, // HTTPS only
SameSite: http.SameSiteStrictMode,
MaxAge: 3600, // 1 hour
Path: "/",
})
🛡️ Authorization
Role-Based Access Control (RBAC)
type Permission string
const (
PermissionRead Permission = "read"
PermissionWrite Permission = "write"
PermissionDelete Permission = "delete"
PermissionAdmin Permission = "admin"
)
type Role struct {
Name string
Permissions []Permission
}
var roles = map[string]Role{
"viewer": {Name: "viewer", Permissions: []Permission{PermissionRead}},
"editor": {Name: "editor", Permissions: []Permission{PermissionRead, PermissionWrite}},
"admin": {Name: "admin", Permissions: []Permission{PermissionRead, PermissionWrite, PermissionDelete, PermissionAdmin}},
}
func hasPermission(userRole string, required Permission) bool {
role, ok := roles[userRole]
if !ok {
return false
}
for _, p := range role.Permissions {
if p == required {
return true
}
}
return false
}
Authorization Middleware
func RequirePermission(permission Permission) Middleware {
return func(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
claims := getUserClaims(r.Context())
if claims == nil {
http.Error(w, "unauthorized", http.StatusUnauthorized)
return
}
if !hasPermission(claims.Role, permission) {
http.Error(w, "forbidden", http.StatusForbidden)
return
}
next.ServeHTTP(w, r)
})
}
}
🔍 Input Validation
Validation Rules
import "github.com/go-playground/validator/v10"
type CreateUserRequest struct {
Name string `json:"name" validate:"required,min=2,max=100"`
Email string `json:"email" validate:"required,email"`
Password string `json:"password" validate:"required,min=8,max=72"`
Age int `json:"age" validate:"omitempty,min=0,max=150"`
}
var validate = validator.New()
func validateRequest(req interface{}) error {
if err := validate.Struct(req); err != nil {
var validationErrors validator.ValidationErrors
if errors.As(err, &validationErrors) {
// Convert to user-friendly messages
return formatValidationErrors(validationErrors)
}
return err
}
return nil
}
SQL Injection Prevention
// ❌ NEVER: String concatenation
query := "SELECT * FROM users WHERE id = " + userInput
// ✅ ALWAYS: Parameterized queries
query := "SELECT * FROM users WHERE id = $1"
row := db.QueryRow(query, userInput)
// ✅ With named parameters (sqlx)
query := "SELECT * FROM users WHERE name = :name AND status = :status"
rows, err := db.NamedQuery(query, map[string]interface{}{
"name": name,
"status": status,
})
XSS Prevention
import "html"
// Escape HTML in output
func sanitizeOutput(input string) string {
return html.EscapeString(input)
}
// In templates, use text/template for auto-escaping
// or html/template for HTML-aware escaping
// React automatically escapes by default
// ❌ Dangerous: bypasses escaping
<div dangerouslySetInnerHTML={{ __html: userInput }} />
// ✅ Safe: auto-escaped
<div>{userInput}</div>
🔑 Secrets Management
Environment Variables
// ❌ NEVER: Hardcoded secrets
const apiKey = "sk-1234567890abcdef"
// ✅ ALWAYS: Environment variables
apiKey := os.Getenv("API_KEY")
if apiKey == "" {
log.Fatal("API_KEY environment variable required")
}
Configuration Structure
type Config struct {
Database struct {
Host string `env:"DB_HOST" envDefault:"localhost"`
Port int `env:"DB_PORT" envDefault:"5432"`
Password string `env:"DB_PASSWORD,required"`
}
JWT struct {
Secret string `env:"JWT_SECRET,required"`
Expiration time.Duration `env:"JWT_EXPIRATION" envDefault:"24h"`
}
}
.gitignore Secrets
# Environment files
.env
.env.local
.env.*.local
# Config files with secrets
config.yaml
secrets/
# Key files
*.pem
*.key
🌐 HTTP Security Headers
func SecurityHeadersMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Prevent MIME sniffing
w.Header().Set("X-Content-Type-Options", "nosniff")
// Prevent clickjacking
w.Header().Set("X-Frame-Options", "DENY")
// Enable XSS filter
w.Header().Set("X-XSS-Protection", "1; mode=block")
// Content Security Policy
w.Header().Set("Content-Security-Policy",
"default-src 'self'; script-src 'self'; style-src 'self' 'unsafe-inline'")
// Strict Transport Security (HTTPS only)
w.Header().Set("Strict-Transport-Security",
"max-age=31536000; includeSubDomains")
// Referrer Policy
w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
next.ServeHTTP(w, r)
})
}
⚡ Rate Limiting
import "golang.org/x/time/rate"
type RateLimiter struct {
limiters map[string]*rate.Limiter
mu sync.RWMutex
rate rate.Limit
burst int
}
func NewRateLimiter(r rate.Limit, b int) *RateLimiter {
return &RateLimiter{
limiters: make(map[string]*rate.Limiter),
rate: r,
burst: b,
}
}
func (rl *RateLimiter) getLimiter(key string) *rate.Limiter {
rl.mu.Lock()
defer rl.mu.Unlock()
limiter, exists := rl.limiters[key]
if !exists {
limiter = rate.NewLimiter(rl.rate, rl.burst)
rl.limiters[key] = limiter
}
return limiter
}
func (rl *RateLimiter) Middleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ip := getClientIP(r)
limiter := rl.getLimiter(ip)
if !limiter.Allow() {
http.Error(w, "rate limit exceeded", http.StatusTooManyRequests)
return
}
next.ServeHTTP(w, r)
})
}
📋 Security Checklist
Authentication
- Passwords hashed with bcrypt/argon2
- Secure session management
- Account lockout after failed attempts
- Password complexity requirements
Authorization
- Principle of least privilege
- Authorization checked on every request
- Resource ownership verified
Input Validation
- All inputs validated and sanitized
- Parameterized queries for SQL
- Output encoding for XSS prevention
Data Protection
- Sensitive data encrypted at rest
- TLS for data in transit
- No secrets in code or logs
Logging & Monitoring
- Security events logged
- No sensitive data in logs
- Alerting for suspicious activity
📚 References
Weekly Installs
2
Repository
aiyuekuang/llmproxyGitHub Stars
11
First Seen
Mar 3, 2026
Security Audits
Installed on
mcpjam2
claude-code2
replit2
junie2
windsurf2
zencoder2