协作头脑风暴
brainstorming
在实现前澄清需求、探索设计选择,并形成清晰的开发方向。
).MatchString(fl.Field().String())\n})\n\ntype Request struct {\n Name string `validate:\"required,safe_string\"`\n}\n```\n\n## Command Injection Prevention\n\n```go\nimport \"os/exec\"\n\n// SAFE - Use exec.Command with separate arguments\ncmd := exec.Command(\"ls\", \"-la\", directory)\noutput, err := cmd.Output()\n\n// SAFE - Use exec.CommandContext for timeout\nctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)\ndefer cancel()\ncmd := exec.CommandContext(ctx, \"ls\", \"-la\", directory)\n\n// UNSAFE - Shell expansion\ncmd := exec.Command(\"sh\", \"-c\", \"ls -la \" + directory) // NEVER with user input!\n\n// UNSAFE - Using os.system equivalent\n// Go doesn't have os.system, but avoid shell=true patterns\n```\n\n## Secure File Upload\n\n```go\nfunc UploadHandler(w http.ResponseWriter, r *http.Request) {\n // Limit request size\n r.Body = http.MaxBytesReader(w, r.Body, 10\u003c\u003c20) // 10 MB\n\n file, header, err := r.FormFile(\"file\")\n if err != nil {\n http.Error(w, \"File too large or invalid\", http.StatusBadRequest)\n return\n }\n defer file.Close()\n\n // Validate content type\n allowedTypes := map[string]bool{\n \"image/jpeg\": true,\n \"image/png\": true,\n \"application/pdf\": true,\n }\n\n buffer := make([]byte, 512)\n file.Read(buffer)\n contentType := http.DetectContentType(buffer)\n file.Seek(0, 0) // Reset reader\n\n if !allowedTypes[contentType] {\n http.Error(w, \"File type not allowed\", http.StatusBadRequest)\n return\n }\n\n // Generate safe filename\n ext := filepath.Ext(header.Filename)\n safeName := fmt.Sprintf(\"%s%s\", uuid.New().String(), ext)\n\n // Save outside web root\n destPath := filepath.Join(uploadDir, safeName)\n dest, err := os.Create(destPath)\n if err != nil {\n http.Error(w, \"Failed to save file\", http.StatusInternalServerError)\n return\n }\n defer dest.Close()\n\n io.Copy(dest, file)\n\n json.NewEncoder(w).Encode(map[string]string{\"filename\": safeName})\n}\n```\n\n## CORS Configuration\n\n### Gin\n\n```go\nimport \"github.com/gin-contrib/cors\"\n\nr := gin.Default()\n\nr.Use(cors.New(cors.Config{\n AllowOrigins: []string{\"https://myapp.com\"},\n AllowMethods: []string{\"GET\", \"POST\", \"PUT\", \"DELETE\"},\n AllowHeaders: []string{\"Authorization\", \"Content-Type\"},\n AllowCredentials: true,\n MaxAge: 12 * time.Hour,\n}))\n```\n\n### Standard Library\n\n```go\nfunc corsMiddleware(next http.Handler) http.Handler {\n return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n origin := r.Header.Get(\"Origin\")\n allowedOrigins := map[string]bool{\"https://myapp.com\": true}\n\n if allowedOrigins[origin] {\n w.Header().Set(\"Access-Control-Allow-Origin\", origin)\n w.Header().Set(\"Access-Control-Allow-Credentials\", \"true\")\n }\n\n if r.Method == \"OPTIONS\" {\n w.Header().Set(\"Access-Control-Allow-Methods\", \"GET, POST, PUT, DELETE\")\n w.Header().Set(\"Access-Control-Allow-Headers\", \"Authorization, Content-Type\")\n w.WriteHeader(http.StatusNoContent)\n return\n }\n\n next.ServeHTTP(w, r)\n })\n}\n```\n\n## Security Headers Middleware\n\n```go\nfunc securityHeaders(next http.Handler) http.Handler {\n return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {\n w.Header().Set(\"X-Content-Type-Options\", \"nosniff\")\n w.Header().Set(\"X-Frame-Options\", \"DENY\")\n w.Header().Set(\"X-XSS-Protection\", \"0\") // Use CSP instead\n w.Header().Set(\"Referrer-Policy\", \"strict-origin-when-cross-origin\")\n w.Header().Set(\"Content-Security-Policy\", \"default-src 'self'\")\n w.Header().Set(\"Strict-Transport-Security\", \"max-age=31536000; includeSubDomains\")\n\n next.ServeHTTP(w, r)\n })\n}\n```\n\n## Rate Limiting\n\n```go\nimport \"golang.org/x/time/rate\"\n\n// Per-IP rate limiter\ntype IPRateLimiter struct {\n ips map[string]*rate.Limiter\n mu *sync.RWMutex\n r rate.Limit\n b int\n}\n\nfunc NewIPRateLimiter(r rate.Limit, b int) *IPRateLimiter {\n return &IPRateLimiter{\n ips: make(map[string]*rate.Limiter),\n mu: &sync.RWMutex{},\n r: r,\n b: b,\n }\n}\n\nfunc (i *IPRateLimiter) GetLimiter(ip string) *rate.Limiter {\n i.mu.Lock()\n defer i.mu.Unlock()\n\n limiter, exists := i.ips[ip]\n if !exists {\n limiter = rate.NewLimiter(i.r, i.b)\n i.ips[ip] = limiter\n }\n\n return limiter\n}\n\n// Middleware\nfunc rateLimitMiddleware(limiter *IPRateLimiter) gin.HandlerFunc {\n return func(c *gin.Context) {\n ip := c.ClientIP()\n if !limiter.GetLimiter(ip).Allow() {\n c.AbortWithStatusJSON(429, gin.H{\"error\": \"Too many requests\"})\n return\n }\n c.Next()\n }\n}\n```\n\n## Secrets Management\n\n```go\nimport \"os\"\n\n// Load from environment\ntype Config struct {\n JWTSecret string\n DatabaseURL string\n APIKey string\n}\n\nfunc LoadConfig() (*Config, error) {\n jwtSecret := os.Getenv(\"JWT_SECRET\")\n if jwtSecret == \"\" {\n return nil, errors.New(\"JWT_SECRET not set\")\n }\n\n return &Config{\n JWTSecret: jwtSecret,\n DatabaseURL: os.Getenv(\"DATABASE_URL\"),\n APIKey: os.Getenv(\"API_KEY\"),\n }, nil\n}\n\n// NEVER hardcode secrets\n// const jwtSecret = \"hardcoded-secret\" // NEVER!\n```\n\n## Logging Security Events\n\n```go\nimport \"log/slog\"\n\nfunc LogLoginAttempt(username string, success bool, ip string) {\n slog.Info(\"login attempt\",\n \"user\", username,\n \"success\", success,\n \"ip\", ip,\n )\n}\n\nfunc LogAccessDenied(userID string, resource string, ip string) {\n slog.Warn(\"access denied\",\n \"user_id\", userID,\n \"resource\", resource,\n \"ip\", ip,\n )\n}\n\n// NEVER log sensitive data\n// slog.Info(\"password\", \"value\", password) // NEVER!\n```\n\n## Anti-Patterns\n\n| Anti-Pattern | Why It's Bad | Correct Approach |\n|--------------|--------------|------------------|\n| `fmt.Sprintf` in SQL | SQL injection | Use parameterized queries |\n| `text/template` for HTML | XSS vulnerability | Use `html/template` |\n| Hardcoded secrets | Secret exposure | Use environment variables |\n| `exec.Command(\"sh\", \"-c\", input)` | Command injection | Use separate arguments |\n| Weak JWT signing | Token forgery | Use HS256 minimum, verify alg |\n| No request size limit | DoS attack | Use `MaxBytesReader` |\n| Using MD5/SHA1 for passwords | Easily cracked | Use bcrypt or argon2 |\n\n## Quick Troubleshooting\n\n| Issue | Likely Cause | Solution |\n|-------|--------------|----------|\n| govulncheck finds CVE | Vulnerable dependency | Update with `go get -u` |\n| JWT validation fails | Wrong signing method | Verify algorithm in ParseWithClaims |\n| CORS error | Origin not allowed | Add origin to allowed list |\n| bcrypt too slow | Cost factor too high | Use cost 10-12 |\n| File upload fails | Size limit exceeded | Increase MaxBytesReader limit |\n| Template not escaping | Using text/template | Switch to html/template |\n\n## Security Scanning Commands\n\n```bash\n# Vulnerability check\ngovulncheck ./...\n\n# Static analysis\nstaticcheck ./...\ngo vet ./...\n\n# Security linter\ngosec ./...\n\n# Dependency check\ngo list -m -json all | nancy sleuth\n\n# Snyk\nsnyk test\n\n# Check for secrets\ngitleaks detect\ntrufflehog git file://.\n```\n\n## Related Skills\n- [OWASP Top 10:2025](../owasp-top-10/SKILL.md)\n- [OWASP General](../owasp/SKILL.md)\n- [Secrets Management](../secrets-management/SKILL.md)\n- [Supply Chain Security](../supply-chain/SKILL.md)\n","licenseDocuments":[]}}
go-security【开发与工程】Go security patterns for web applications. Covers dependency auditing, secure coding practices, crypto, and OWASP for Go ecosystem. USE WHEN: user works with "Go", "Golang", "Gin", "Fiber", "Echo", asks about "Go vulnerabilities", "Go modules security", "Go injection", "Go authentication" DO NOT USE FOR: general OWASP concepts - use `owasp` or `owa…
独立 GitHub 快照 · 自动分类 · 以原作者文档为准
name: go-security description: | Go security patterns for web applications. Covers dependency auditing, secure coding practices, crypto, and OWASP for Go ecosystem.
USE WHEN: user works with "Go", "Golang", "Gin", "Fiber", "Echo", asks about "Go vulnerabilities", "Go modules security", "Go injection", "Go authentication"
owasp or owasp-top-10 instead, other language security - use language-specific skills
allowed-tools: Read, Grep, Glob, Bashowasp or owasp-top-10 skilljava-security skillpython-security skillsecrets-management skillDeep Knowledge: Use
mcp__documentation__fetch_docswith technology:gofor Go security documentation.
# Go built-in vulnerability check (Go 1.18+)
go list -m -json all | go run golang.org/x/vuln/cmd/govulncheck@latest
# govulncheck direct
govulncheck ./...
# Check for outdated modules
go list -u -m all
# Verify module checksums
go mod verify
# Snyk for Go
snyk test
# GitHub Actions
- name: Security audit
run: |
go install golang.org/x/vuln/cmd/govulncheck@latest
govulncheck ./...
- name: Snyk scan
uses: snyk/actions/golang@master
with:
args: --severity-threshold=high
// SAFE - Parameterized query with ?
row := db.QueryRow("SELECT * FROM users WHERE email = ?", email)
// SAFE - Parameterized query with $n (PostgreSQL)
row := db.QueryRow("SELECT * FROM users WHERE email = $1", email)
// SAFE - Named parameters with sqlx
row := db.NamedQuery("SELECT * FROM users WHERE email = :email",
map[string]interface{}{"email": email})
// UNSAFE - String formatting
query := fmt.Sprintf("SELECT * FROM users WHERE email = '%s'", email) // NEVER!
row := db.QueryRow(query)
// UNSAFE - String concatenation
query := "SELECT * FROM users WHERE email = '" + email + "'" // NEVER!
// SAFE - GORM where clause
var user User
db.Where("email = ?", email).First(&user)
// SAFE - GORM struct condition
db.Where(&User{Email: email}).First(&user)
// SAFE - GORM map condition
db.Where(map[string]interface{}{"email": email}).First(&user)
// UNSAFE - Raw with formatting
db.Raw(fmt.Sprintf("SELECT * FROM users WHERE email = '%s'", email)) // NEVER!
// SAFE - html/template auto-escapes
import "html/template"
tmpl := template.Must(template.ParseFiles("page.html"))
tmpl.Execute(w, data) // data.UserInput is auto-escaped
<!-- Template - auto-escaped -->
<p>{{.UserInput}}</p>
// UNSAFE for HTML - text/template does NOT escape
import "text/template" // Only for non-HTML content!
import "html"
// Escape HTML entities
safeString := html.EscapeString(userInput)
// For rich HTML, use bluemonday
import "github.com/microcosm-cc/bluemonday"
p := bluemonday.UGCPolicy()
safeHTML := p.Sanitize(userInput)
import (
"github.com/golang-jwt/jwt/v5"
"time"
)
var jwtKey = []byte(os.Getenv("JWT_SECRET"))
type Claims struct {
UserID string `json:"user_id"`
Email string `json:"email"`
jwt.RegisteredClaims
}
func GenerateToken(userID, email string) (string, error) {
claims := &Claims{
UserID: userID,
Email: email,
RegisteredClaims: jwt.RegisteredClaims{
ExpiresAt: jwt.NewNumericDate(time.Now().Add(time.Hour)),
IssuedAt: jwt.NewNumericDate(time.Now()),
Issuer: "myapp",
},
}
token := jwt.NewWithClaims(jwt.SigningMethodHS256, claims)
return token.SignedString(jwtKey)
}
func ValidateToken(tokenString string) (*Claims, error) {
claims := &Claims{}
token, err := jwt.ParseWithClaims(tokenString, claims,
func(token *jwt.Token) (interface{}, error) {
// Validate signing method
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, fmt.Errorf("unexpected signing method")
}
return jwtKey, nil
})
if err != nil || !token.Valid {
return nil, err
}
return claims, nil
}
import "golang.org/x/crypto/bcrypt"
func HashPassword(password string) (string, error) {
// Cost 12 is recommended
bytes, err := bcrypt.GenerateFromPassword([]byte(password), 12)
return string(bytes), err
}
func CheckPassword(password, hash string) bool {
err := bcrypt.CompareHashAndPassword([]byte(hash), []byte(password))
return err == nil
}
import "golang.org/x/crypto/argon2"
func HashPasswordArgon2(password string) (string, error) {
salt := make([]byte, 16)
if _, err := rand.Read(salt); err != nil {
return "", err
}
hash := argon2.IDKey([]byte(password), salt, 1, 64*1024, 4, 32)
// Encode for storage
return base64.StdEncoding.EncodeToString(append(salt, hash...)), nil
}
import "github.com/go-playground/validator/v10"
type CreateUserRequest struct {
Email string `json:"email" validate:"required,email,max=255"`
Password string `json:"password" validate:"required,min=12,max=128,containsany=ABCDEFGHIJKLMNOPQRSTUVWXYZ,containsany=abcdefghijklmnopqrstuvwxyz,containsany=0123456789,containsany=@$!%*?&"`
Name string `json:"name" validate:"required,min=2,max=100,alpha"`
}
var validate = validator.New()
func CreateUser(c *gin.Context) {
var req CreateUserRequest
if err := c.ShouldBindJSON(&req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
if err := validate.Struct(req); err != nil {
c.JSON(400, gin.H{"error": err.Error()})
return
}
// req is validated
}
// Register custom validation
validate.RegisterValidation("safe_string", func(fl validator.FieldLevel) bool {
return regexp.MustCompile(`^[a-zA-Z\s\-']+
go-security · Skills-SI 市场
).MatchString(fl.Field().String())
})
type Request struct {
Name string `validate:"required,safe_string"`
}
Command Injection Prevention
import "os/exec"
// SAFE - Use exec.Command with separate arguments
cmd := exec.Command("ls", "-la", directory)
output, err := cmd.Output()
// SAFE - Use exec.CommandContext for timeout
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
cmd := exec.CommandContext(ctx, "ls", "-la", directory)
// UNSAFE - Shell expansion
cmd := exec.Command("sh", "-c", "ls -la " + directory) // NEVER with user input!
// UNSAFE - Using os.system equivalent
// Go doesn't have os.system, but avoid shell=true patterns
Secure File Upload
func UploadHandler(w http.ResponseWriter, r *http.Request) {
// Limit request size
r.Body = http.MaxBytesReader(w, r.Body, 10<<20) // 10 MB
file, header, err := r.FormFile("file")
if err != nil {
http.Error(w, "File too large or invalid", http.StatusBadRequest)
return
}
defer file.Close()
// Validate content type
allowedTypes := map[string]bool{
"image/jpeg": true,
"image/png": true,
"application/pdf": true,
}
buffer := make([]byte, 512)
file.Read(buffer)
contentType := http.DetectContentType(buffer)
file.Seek(0, 0) // Reset reader
if !allowedTypes[contentType] {
http.Error(w, "File type not allowed", http.StatusBadRequest)
return
}
// Generate safe filename
ext := filepath.Ext(header.Filename)
safeName := fmt.Sprintf("%s%s", uuid.New().String(), ext)
// Save outside web root
destPath := filepath.Join(uploadDir, safeName)
dest, err := os.Create(destPath)
if err != nil {
http.Error(w, "Failed to save file", http.StatusInternalServerError)
return
}
defer dest.Close()
io.Copy(dest, file)
json.NewEncoder(w).Encode(map[string]string{"filename": safeName})
}
CORS Configuration
Gin
import "github.com/gin-contrib/cors"
r := gin.Default()
r.Use(cors.New(cors.Config{
AllowOrigins: []string{"https://myapp.com"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowHeaders: []string{"Authorization", "Content-Type"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}))
Standard Library
func corsMiddleware(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
origin := r.Header.Get("Origin")
allowedOrigins := map[string]bool{"https://myapp.com": true}
if allowedOrigins[origin] {
w.Header().Set("Access-Control-Allow-Origin", origin)
w.Header().Set("Access-Control-Allow-Credentials", "true")
}
if r.Method == "OPTIONS" {
w.Header().Set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
w.Header().Set("Access-Control-Allow-Headers", "Authorization, Content-Type")
w.WriteHeader(http.StatusNoContent)
return
}
next.ServeHTTP(w, r)
})
}
Security Headers Middleware
func securityHeaders(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
w.Header().Set("X-Content-Type-Options", "nosniff")
w.Header().Set("X-Frame-Options", "DENY")
w.Header().Set("X-XSS-Protection", "0") // Use CSP instead
w.Header().Set("Referrer-Policy", "strict-origin-when-cross-origin")
w.Header().Set("Content-Security-Policy", "default-src 'self'")
w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
next.ServeHTTP(w, r)
})
}
Rate Limiting
import "golang.org/x/time/rate"
// Per-IP rate limiter
type IPRateLimiter struct {
ips map[string]*rate.Limiter
mu *sync.RWMutex
r rate.Limit
b int
}
func NewIPRateLimiter(r rate.Limit, b int) *IPRateLimiter {
return &IPRateLimiter{
ips: make(map[string]*rate.Limiter),
mu: &sync.RWMutex{},
r: r,
b: b,
}
}
func (i *IPRateLimiter) GetLimiter(ip string) *rate.Limiter {
i.mu.Lock()
defer i.mu.Unlock()
limiter, exists := i.ips[ip]
if !exists {
limiter = rate.NewLimiter(i.r, i.b)
i.ips[ip] = limiter
}
return limiter
}
// Middleware
func rateLimitMiddleware(limiter *IPRateLimiter) gin.HandlerFunc {
return func(c *gin.Context) {
ip := c.ClientIP()
if !limiter.GetLimiter(ip).Allow() {
c.AbortWithStatusJSON(429, gin.H{"error": "Too many requests"})
return
}
c.Next()
}
}
Secrets Management
import "os"
// Load from environment
type Config struct {
JWTSecret string
DatabaseURL string
APIKey string
}
func LoadConfig() (*Config, error) {
jwtSecret := os.Getenv("JWT_SECRET")
if jwtSecret == "" {
return nil, errors.New("JWT_SECRET not set")
}
return &Config{
JWTSecret: jwtSecret,
DatabaseURL: os.Getenv("DATABASE_URL"),
APIKey: os.Getenv("API_KEY"),
}, nil
}
// NEVER hardcode secrets
// const jwtSecret = "hardcoded-secret" // NEVER!
Logging Security Events
import "log/slog"
func LogLoginAttempt(username string, success bool, ip string) {
slog.Info("login attempt",
"user", username,
"success", success,
"ip", ip,
)
}
func LogAccessDenied(userID string, resource string, ip string) {
slog.Warn("access denied",
"user_id", userID,
"resource", resource,
"ip", ip,
)
}
// NEVER log sensitive data
// slog.Info("password", "value", password) // NEVER!
Anti-Patterns
Anti-Pattern Why It's Bad Correct Approach fmt.Sprintf in SQLSQL injection Use parameterized queries text/template for HTMLXSS vulnerability Use html/template Hardcoded secrets Secret exposure Use environment variables exec.Command("sh", "-c", input)Command injection Use separate arguments Weak JWT signing Token forgery Use HS256 minimum, verify alg No request size limit DoS attack Use MaxBytesReader Using MD5/SHA1 for passwords Easily cracked Use bcrypt or argon2
Quick Troubleshooting
Issue Likely Cause Solution govulncheck finds CVE Vulnerable dependency Update with go get -u JWT validation fails Wrong signing method Verify algorithm in ParseWithClaims CORS error Origin not allowed Add origin to allowed list bcrypt too slow Cost factor too high Use cost 10-12 File upload fails Size limit exceeded Increase MaxBytesReader limit Template not escaping Using text/template Switch to html/template
Security Scanning Commands
# Vulnerability check
govulncheck ./...
# Static analysis
staticcheck ./...
go vet ./...
# Security linter
gosec ./...
# Dependency check
go list -m -json all | nancy sleuth
# Snyk
snyk test
# Check for secrets
gitleaks detect
trufflehog git file://.
Related Skills