go-security-audit

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Go Security Audit

Go应用安全审计

Security is not a feature — it's a property. Every line of code either maintains it or degrades it.
安全不是一项功能,而是系统的固有属性。每一行代码要么维护安全性,要么削弱它。

1. Input Validation

1. 输入验证

NEVER trust user input. Validate at the boundary:

绝对不要信任用户输入,在边界处进行验证:

go
// ✅ Good — validate before use
func (h *Handler) handleCreate(w http.ResponseWriter, r *http.Request) {
    // Limit body size
    r.Body = http.MaxBytesReader(w, r.Body, 1<<20) // 1 MB

    var req CreateRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        respondError(w, http.StatusBadRequest, "invalid JSON")
        return
    }

    if err := validate.Struct(req); err != nil {
        respondError(w, http.StatusBadRequest, "validation failed")
        return
    }
    // proceed with validated data
}
go
// ✅ 最佳实践 — 使用前验证
func (h *Handler) handleCreate(w http.ResponseWriter, r *http.Request) {
    // 限制请求体大小
    r.Body = http.MaxBytesReader(w, r.Body, 1<<20) // 1 MB

    var req CreateRequest
    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
        respondError(w, http.StatusBadRequest, "无效JSON格式")
        return
    }

    if err := validate.Struct(req); err != nil {
        respondError(w, http.StatusBadRequest, "验证失败")
        return
    }
    // 使用验证后的数据继续处理
}

String sanitization:

字符串清理:

go
// Sanitize HTML to prevent XSS
import "github.com/microcosm-cc/bluemonday"

p := bluemonday.UGCPolicy()
sanitized := p.Sanitize(userInput)

// Validate email format
import "net/mail"
_, err := mail.ParseAddress(email)

// Validate URLs
u, err := url.Parse(input)
if err != nil || (u.Scheme != "http" && u.Scheme != "https") {
    // reject
}
go
// 清理HTML以防止XSS攻击
import "github.com/microcosm-cc/bluemonday"

p := bluemonday.UGCPolicy()
sanitized := p.Sanitize(userInput)

// 验证邮箱格式
import "net/mail"
_, err := mail.ParseAddress(email)

// 验证URL
u, err := url.Parse(input)
if err != nil || (u.Scheme != "http" && u.Scheme != "https") {
    // 拒绝该请求
}

2. SQL Injection Prevention

2. SQL注入防护

ALWAYS use parameterized queries:

始终使用参数化查询:

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

// ✅ Good — with sqlx named params
query := "SELECT * FROM users WHERE name = :name AND age > :age"
rows, err := db.NamedQueryContext(ctx, query, map[string]interface{}{
    "name": name,
    "age":  minAge,
})

// ❌ CRITICAL — string concatenation = SQL injection
query := "SELECT * FROM users WHERE email = '" + email + "'"
query := fmt.Sprintf("SELECT * FROM users WHERE id = %s", id)
go
// ✅ 最佳实践 — 参数化查询
row := db.QueryRowContext(ctx,
    "SELECT id, name FROM users WHERE email = $1", email)

// ✅ 最佳实践 — 使用sqlx命名参数
query := "SELECT * FROM users WHERE name = :name AND age > :age"
rows, err := db.NamedQueryContext(ctx, query, map[string]interface{}{
    "name": name,
    "age":  minAge,
})

// ❌ 严重风险 — 字符串拼接易引发SQL注入
query := "SELECT * FROM users WHERE email = '" + email + "'"
query := fmt.Sprintf("SELECT * FROM users WHERE id = %s", id)

Dynamic queries:

动态查询:

When building dynamic WHERE clauses, use query builders or safe concatenation:
go
// ✅ Good — safe dynamic query building
var conditions []string
var args []interface{}
argIdx := 1

if name != "" {
    conditions = append(conditions, fmt.Sprintf("name = $%d", argIdx))
    args = append(args, name)
    argIdx++
}

query := "SELECT * FROM users"
if len(conditions) > 0 {
    query += " WHERE " + strings.Join(conditions, " AND ")
}
构建动态WHERE子句时,使用查询构建器或安全的拼接方式:
go
// ✅ 最佳实践 — 安全的动态查询构建
var conditions []string
var args []interface{}
argIdx := 1

if name != "" {
    conditions = append(conditions, fmt.Sprintf("name = $%d", argIdx))
    args = append(args, name)
    argIdx++
}

query := "SELECT * FROM users"
if len(conditions) > 0 {
    query += " WHERE " + strings.Join(conditions, " AND ")
}

3. Authentication & Authorization

3. 身份验证与授权

Password handling:

密码处理:

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

// Hash password
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)

// Verify password — constant-time comparison built in
err := bcrypt.CompareHashAndPassword(hash, []byte(password))
NEVER store plaintext passwords. NEVER use MD5/SHA for passwords.
go
import "golang.org/x/crypto/bcrypt"

// 哈希密码
hash, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)

// 验证密码 — 内置恒时比较机制
err := bcrypt.CompareHashAndPassword(hash, []byte(password))
绝对不要存储明文密码。绝对不要使用MD5/SHA算法处理密码。

JWT validation:

JWT验证:

go
// ✅ Always validate:
// 1. Signature (algorithm must match expectation)
// 2. Expiration (exp claim)
// 3. Issuer (iss claim)
// 4. Audience (aud claim)

// ❌ CRITICAL — never disable signature verification
// ❌ CRITICAL — never accept "alg": "none"
// ❌ CRITICAL — never hardcode signing keys in source code
go
// ✅ 必须验证以下内容:
// 1. 签名(算法必须与预期一致)
// 2. 过期时间(exp声明)
// 3. 签发者(iss声明)
// 4. 受众(aud声明)

// ❌ 严重风险 — 绝不能禁用签名验证
// ❌ 严重风险 — 绝不能接受"alg": "none"
// ❌ 严重风险 — 绝不能在源代码中硬编码签名密钥

Authorization middleware:

授权中间件:

go
func RequireRole(role string) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            user := UserFromContext(r.Context())
            if user == nil || !user.HasRole(role) {
                http.Error(w, "forbidden", http.StatusForbidden)
                return
            }
            next.ServeHTTP(w, r)
        })
    }
}
go
func RequireRole(role string) func(http.Handler) http.Handler {
    return func(next http.Handler) http.Handler {
        return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
            user := UserFromContext(r.Context())
            if user == nil || !user.HasRole(role) {
                http.Error(w, "禁止访问", http.StatusForbidden)
                return
            }
            next.ServeHTTP(w, r)
        })
    }
}

4. Secrets Management

4. 密钥管理

Rules:

规则:

  • 🔴 NEVER hardcode secrets, tokens, or API keys in source code
  • 🔴 NEVER commit secrets to git (even in "test" files)
  • 🔴 NEVER log secrets, tokens, or passwords
go
// ✅ Good — from environment
dbURL := os.Getenv("DATABASE_URL")

// ✅ Good — from secrets manager
secret, err := secretsManager.GetSecret(ctx, "api-key")

// ❌ CRITICAL
const apiKey = "sk-1234567890abcdef" // hardcoded secret
  • 🔴 绝对不要在源代码中硬编码密钥、令牌或API密钥
  • 🔴 绝对不要将密钥提交到git仓库(即使是在"测试"文件中)
  • 🔴 绝对不要记录密钥、令牌或密码
go
// ✅ 最佳实践 — 从环境变量获取
dbURL := os.Getenv("DATABASE_URL")

// ✅ 最佳实践 — 从密钥管理服务获取
secret, err := secretsManager.GetSecret(ctx, "api-key")

// ❌ 严重风险
const apiKey = "sk-1234567890abcdef" // 硬编码密钥

Use
.gitignore
:

使用
.gitignore

.env
*.pem
*.key
credentials.json
.env
*.pem
*.key
credentials.json

Scan for leaked secrets:

扫描泄露的密钥:

bash
undefined
bash
undefined

Use gitleaks in CI

在CI流程中使用gitleaks

gitleaks detect --source=. --verbose
undefined
gitleaks detect --source=. --verbose
undefined

5. HTTP Security Headers

5. HTTP安全头

go
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("Content-Security-Policy", "default-src 'self'")
        w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
        w.Header().Set("X-XSS-Protection", "0") // modern browsers handle this
        next.ServeHTTP(w, r)
    })
}
go
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("Content-Security-Policy", "default-src 'self'")
        w.Header().Set("Strict-Transport-Security", "max-age=31536000; includeSubDomains")
        w.Header().Set("X-XSS-Protection", "0") // 现代浏览器已自行处理
        next.ServeHTTP(w, r)
    })
}

6. TLS Configuration

6. TLS配置

go
tlsConfig := &tls.Config{
    MinVersion: tls.VersionTLS12,
    CipherSuites: []uint16{
        tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
        tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
    },
    PreferServerCipherSuites: true,
}

srv := &http.Server{
    TLSConfig: tlsConfig,
    // ...
}
go
tlsConfig := &tls.Config{
    MinVersion: tls.VersionTLS12,
    CipherSuites: []uint16{
        tls.TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,
        tls.TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,
    },
    PreferServerCipherSuites: true,
}

srv := &http.Server{
    TLSConfig: tlsConfig,
    // ...
}

7. Rate Limiting

7. 速率限制

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

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

func (rl *RateLimiter) Allow(key string) bool {
    limiter, _ := rl.limiters.LoadOrStore(key,
        rate.NewLimiter(rl.rate, rl.burst))
    return limiter.(*rate.Limiter).Allow()
}
Apply rate limiting to auth endpoints, public APIs, and any resource-intensive operations.
go
import "golang.org/x/time/rate"

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

func (rl *RateLimiter) Allow(key string) bool {
    limiter, _ := rl.limiters.LoadOrStore(key,
        rate.NewLimiter(rl.rate, rl.burst))
    return limiter.(*rate.Limiter).Allow()
}
对身份验证端点、公开API以及任何资源密集型操作应用速率限制。

8. Logging Security

8. 日志安全

go
// ❌ CRITICAL — logging sensitive data
log.Printf("user login: email=%s password=%s", email, password)
log.Printf("auth token: %s", token)
log.Printf("request body: %v", req) // may contain secrets

// ✅ Good — redact sensitive fields
log.Printf("user login: email=%s", email)
logger.Info("auth completed", zap.String("user_id", userID))
go
// ❌ 严重风险 — 记录敏感数据
log.Printf("用户登录:email=%s password=%s", email, password)
log.Printf("身份验证令牌:%s", token)
log.Printf("请求体:%v", req) // 可能包含敏感信息

// ✅ 最佳实践 — 编辑敏感字段
log.Printf("用户登录:email=%s", email)
logger.Info("身份验证完成", zap.String("user_id", userID))

Security Audit Checklist

安全审计检查清单

Critical (🔴 BLOCKER)

关键项(🔴 阻塞问题)

  • No SQL injection vectors (all queries parameterized)
  • No hardcoded secrets/keys/tokens
  • No plaintext password storage
  • No disabled TLS certificate verification
  • Request body size limited
  • JWT signature verified,
    alg: none
    rejected
  • 无SQL注入风险(所有查询均为参数化)
  • 无硬编码密钥/令牌/API密钥
  • 无明文密码存储
  • 无禁用TLS证书验证的情况
  • 请求体大小已限制
  • JWT签名已验证,
    alg: none
    已被拒绝

Important (🟡 WARNING)

重要项(🟡 警告)

  • Input validation on all external data
  • Rate limiting on auth and public endpoints
  • Security headers set on all responses
  • CORS configured restrictively
  • Error messages don't leak internals
  • Audit logging for auth events
  • 所有外部数据均经过输入验证
  • 身份验证和公开API端点已配置速率限制
  • 所有响应均设置了安全头
  • CORS配置严格
  • 错误信息未泄露内部细节
  • 身份验证事件已记录审计日志

Recommended (🟢 SUGGESTION)

推荐项(🟢 建议)

  • govulncheck
    in CI pipeline
  • gitleaks
    for secret scanning
  • Structured logging with redaction
  • Dependency pinning with verified checksums
  • CI流水线中集成
    govulncheck
  • 使用
    gitleaks
    进行密钥扫描
  • 使用带编辑功能的结构化日志
  • 依赖项固定并使用已验证的校验和