mcpField
G
go-security-audit

go-security-audit

github.com/eduardo-slセキュリティ83107 回インストール2 週間前に更新
リポジトリ

go-security-audit について

go-security-auditは、Go開発者が変更箇所やコードベースを調べるためのセキュリティ監査スキルです。スキャナーの結果と手動確認を組み合わせ、重要度順に指摘をまとめます。

主な機能

  1. 01対象限定・差分・全面監査モード
  2. 02govulncheck、gosec、go vetの実行案内
  3. 03入力処理、インジェクション、アクセス制御の確認
  4. 04シークレット、TLS、ヘッダー、レート制限、ログの確認
  5. 05指摘にファイル行番号、入力経路、修正案を記載

活用シーン

  1. 01PRのマージ前にGoの変更をレビュー
  2. 02Goサービスの一般的なセキュリティ上の問題を監査
  3. 03認証や入力処理の経路を確認
  4. 04優先度付きのセキュリティ報告を作成

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/eduardo-sl/go-agent-skills --skill go-security-audit

インストール後、Claude Code で/go-security-auditを入力して読み込まれたか確認します。

詳細

Go セキュリティ監査

セキュリティは機能ではなく、特性です。コードの各行が、セキュリティを維持するか、損なうかのどちらかです。

実施モード

開始前に、依頼に合ったモードを選んでください。

  • 対象を絞ったチェック — 単一の懸念事項(「このクエリはインジェクションの危険がありますか?」「この認証ミドルウェアをレビューしてください」など)。該当するセクションのみを適用します。
  • 差分監査 — PR または作業ツリーで変更された行について、以下のすべての懸念事項を監査します。
  • 全体監査(「サービスのセキュリティレビュー」の既定)—「大規模コードベースの監査」にある並行パスを使用して、コードベース全体を調べます。

まずスキャナーを実行する

手動レビューの前に、自動スキャナーを実行し、その出力を検出事項に反映してください(インストールされていないものはスキップし、その旨を記載してください)。

govulncheck ./...       # known CVEs actually reachable from your code
gosec ./...             # static analysis for insecure patterns
go vet ./...            # includes some security-relevant checks

スキャナーは既知のパターンを検出します。以下の手動パスでは、スキャナーでは見つけられないロジック上の欠陥を探します。

大規模コードベースの監査

以下の各番号付きセクションは、それぞれ独立した監査パスです。およそ20ファイルを超えるコードベースでは、次の手順に従ってください。

  1. まず攻撃対象領域を特定します。HTTP/gRPC ハンドラー、CLI エントリーポイント、キューのコンシューマー、外部入力を解析するものなどが対象です。
  2. 懸念事項ごとに1つのパスを実行します:(a)入力検証とインジェクション、(b)認証と認可、(c)シークレットと暗号化、(d)TLS とセキュリティヘッダーとレート制限、(e)ログの適切な取り扱い。
  3. 環境で並行するサブエージェントまたはタスクへの作業委任がサポートされている場合は、各パスをそれぞれ1つに割り当てます。各パスは重複しません。サポートされていない場合は、順番に実行します。
  4. 各検出事項には、file.go:line、脆弱な入力経路、および具体的な修正方法を記載する必要があります。重大度順に並べた1つのレポートにまとめます。

1. 入力検証

ユーザー入力は決して信頼しないでください。境界で検証します。
// ✅ 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
}
文字列のサニタイズ:
// 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
}

2. SQL インジェクションの防止

必ずパラメーター化クエリを使用してください。
// ✅ 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)
動的クエリ:

動的な WHERE 句を構築する場合は、クエリビルダーまたは安全な連結を使用してください。

// ✅ 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 ")
}

3. 認証と認可

パスワードの取り扱い:
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))

平文のパスワードは決して保存しないでください。パスワードに MD5/SHA を決して使用しないでください。

JWT の検証:
// ✅ 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
認可ミドルウェア:
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)
        })
    }
}

4. シークレット管理

ルール:
  • 🔴 ソースコードにシークレット、トークン、API キーをハードコードしないでください
  • 🔴 「test」ファイルであっても、シークレットを git にコミットしないでください
  • 🔴 シークレット、トークン、パスワードをログに記録しないでください
// ✅ 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
.gitignore を使用する:
.env
*.pem
*.key
credentials.json
漏えいしたシークレットをスキャンする:
# Use gitleaks in CI
gitleaks detect --source=. --verbose

5. HTTP セキュリティヘッダー

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)
    })
}

6. TLS 設定

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. レート制限

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. ログのセキュリティ

// ❌ 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", slog.String("user_id", userID))

セキュリティ監査チェックリスト

重大(🔴 BLOCKER)
  • SQL インジェクションの攻撃経路がない(すべてのクエリがパラメーター化されている)
  • ハードコードされたシークレット/キー/トークンがない
  • 平文のパスワードが保存されていない
  • TLS 証明書の検証が無効化されていない
  • リクエスト本文のサイズが制限されている
  • JWT の署名が検証され、alg: none が拒否される
重要(🟡 WARNING)
  • すべての外部データで入力検証が行われている
  • 認証エンドポイントと公開エンドポイントにレート制限がある
  • すべてのレスポンスにセキュリティヘッダーが設定されている
  • CORS が制限的に設定されている
  • エラーメッセージから内部情報が漏えいしない
  • 認証イベントが監査ログに記録されている
推奨(🟢 SUGGESTION)
  • CI パイプラインで govulncheck を実行する
  • シークレットスキャンに gitleaks を使用する
  • レダクションを適用した構造化ログを使用する
  • 検証済みチェックサムを使って依存関係のバージョンを固定する

コメント