gosec は Go コードのセキュリティ上の問題を静的解析で検出するツールです。
解決する問題
SQL インジェクション、ハードコードされた認証情報、安全でない暗号アルゴリズムの使用など、コードレビューで見落としがちなセキュリティ脆弱性を自動的に検出します。
1 2 3 4 5 6 7 8 9 10 11 12
| func FindUser(db *sql.DB, name string) (*User, error) { query := fmt.Sprintf("SELECT * FROM users WHERE name = '%s'", name) row := db.QueryRow(query)
var user User if err := row.Scan(&user.ID, &user.Name); err != nil { return nil, fmt.Errorf("scan user: %w", err) } return &user, nil }
|
1 2 3 4 5 6 7 8 9 10
| func FindUser(db *sql.DB, name string) (*User, error) { row := db.QueryRow("SELECT * FROM users WHERE name = $1", name)
var user User if err := row.Scan(&user.ID, &user.Name); err != nil { return nil, fmt.Errorf("scan user: %w", err) } return &user, nil }
|
設定
公式ドキュメント
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| version: "2"
linters: enable: - gosec settings: gosec: includes: - G101 - G201 - G202 - G204 - G304 - G401 - G402 - G404 - G501
|
オプション
| オプション |
型 |
デフォルト |
説明 |
includes |
[]string |
[](全ルール) |
チェックするルール ID のリスト |
excludes |
[]string |
[] |
除外するルール ID のリスト |
exclude-generated |
bool |
false |
生成されたコードを除外する |
severity |
string |
"" |
最小重大度でフィルタ(low, medium, high) |
confidence |
string |
"" |
最小信頼度でフィルタ(low, medium, high) |
config |
map |
{} |
ルールごとの詳細設定 |
主要なルール
認証情報・機密情報
| ルール |
検出内容 |
| G101 |
ハードコードされた認証情報(パスワード、API キー、トークン) |
SQL・コマンドインジェクション
| ルール |
検出内容 |
| G201 |
fmt.Sprintf を使った SQL クエリ構築 |
| G202 |
文字列結合を使った SQL クエリ構築 |
| G203 |
HTML テンプレートでのエスケープされていないデータ |
| G204 |
外部コマンド実行(exec.Command に変数を渡す) |
ファイル操作
| ルール |
検出内容 |
| G301 |
ディレクトリ作成時の不適切なパーミッション |
| G302 |
ファイル作成・chmod 時の不適切なパーミッション |
| G304 |
ユーザー入力によるファイルパス指定(パストラバーサル) |
| G305 |
ZIP 展開時のパストラバーサル |
| G306 |
ファイル書き込み時の不適切なパーミッション |
暗号
| ルール |
検出内容 |
| G401 |
MD5 / SHA1 の使用 |
| G402 |
安全でない TLS 設定 |
| G403 |
RSA 鍵長が 2048 ビット未満 |
| G404 |
math/rand の使用(crypto/rand を使うべき) |
その他
| ルール |
検出内容 |
| G104 |
未チェックのエラー |
| G107 |
HTTP リクエストの URL にユーザー入力(SSRF) |
| G112 |
ReadHeaderTimeout 未設定の HTTP サーバー |
| G115 |
整数型変換によるオーバーフロー |
サンプル
検出例
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| const apiKey = "sk-proj-ABCDEF123456"
func ReadFile(r *http.Request) ([]byte, error) { path := r.URL.Query().Get("file") return os.ReadFile(path)
}
func GenerateToken() string { return fmt.Sprintf("%d", rand.Int()) }
|
修正例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| func APIKey() string { return os.Getenv("API_KEY") }
func ReadFile(r *http.Request) ([]byte, error) { path := r.URL.Query().Get("file") cleanPath := filepath.Clean(path) if !strings.HasPrefix(cleanPath, "/allowed/dir/") { return nil, errors.New("access denied") } return os.ReadFile(cleanPath) }
func GenerateToken() (string, error) { b := make([]byte, 32) if _, err := crypto_rand.Read(b); err != nil { return "", fmt.Errorf("generate token: %w", err) } return hex.EncodeToString(b), nil }
|
注意点
- 誤検知を抑制するには
//nolint:gosec またはコード中に // #nosec G404 のようにルール ID 付きで記述する
- テストコードでは
math/rand やハードコード値が必要な場面があるため、issues.exclude-rules でテストファイルを除外するとよい
includes と excludes は排他的。両方指定した場合は includes が優先される
参考リンク