Blogへ戻る

Language: 日本語

「セキュリティ対策、後回しにしていませんか?」 小さな穴を確実に塞ぎ切る 4 つの堅牢化プラクティス

「secretlint を入れてクレデンシャル漏洩を防ぎたい」「BAN されたユーザーの古いトークン操作を無効化したい」「CI のログに機密情報が流出しないようにしたい」……。

「secretlint を入れてクレデンシャル漏洩を防ぎたい」「BAN されたユーザーの古いトークン操作を無効化したい」「CI のログに機密情報が流出しないようにしたい」……。

そう思いつつも、日々の機能開発やバグ修正に追われて 「重要だけど緊急ではないセキュリティ対応」が後回し になっていませんか?

セキュリティの深刻なインシデントは、未知のゼロデイ攻撃よりも、「誰もが薄々気づいていたけれど放置されていた小さな未対応事項」が重なった瞬間に発生 します。

本番システムのセキュリティ監査を実施した際、単発では小さく見えても確実に穴になり得る 4 つの堅牢化(Hardening) をまとめて実施しました。

この記事では、secretlint を pre-commit ではなく pre-push に置く運用上の理由から、Google OAuth による BAN ユーザー再認証、推移的依存の最小侵襲パッチ、そして CI 出力のフェイルクローズなサニタイズまでを解説します。

得られること

  • secretlint を pre-commit ではなく pre-push に置くべき運用上の理由と、ダミー認証情報の allowlist 設計がわかる
  • BAN されたユーザーが過去のトークンで不正操作するのを防ぐ、Google OAuth ID token の再認証フローが学べる
  • Terraform plan 失敗時の情報漏洩を防ぎ、監査ログの最小項目のみ安全に開示する CI 出力設計が身につく!

1. secretlint を「pre-push」に置く運用上の必然性

従来のリポジトリでは、秘密鍵ファイル(*.pem 等)の誤コミットを検知する単純なスクリプトしかなく、API キーや Google Cloud のサービスアカウントキー、OAuth トークンなどの一般的なシークレットに対する漏洩検査が空白のままでした。

そこで、網羅的なシークレット検知ツールである secretlint を導入しました。

# pre-push hook で secretlint を実行
npx secretlint --secretlintignore .gitignore "**/*"

なぜ pre-commit ではなく pre-push なのか?

多くのチュートリアルでは pre-commit への配置が推奨されますが、コミットのたびに全ファイルスキャンが走ると 開発者の体感レスポンスが悪化し、最終的に --no-verify でフックを無効化される原因 になります。

外界(GitHub やリモートリポジトリ)との真の境界は git push の瞬間です。日々のローカルコミットは軽快に行わせつつ、リモートへ送り出す最後の砦として pre-push に寄せるのが、開発速度とセキュリティを両立させる最も現実的な設計です。

また、テストコードや開発環境に含まれるダミー認証情報については、ルール全体を無効化するのではなく、該当サブルールの allows オプションで個別にホワイトリスト登録し、検査の形骸化を防止しました。

2. BAN ユーザーの削除申請経路を Google OAuth に一本化

利用規約違反等でアカウントが BAN されたユーザーから「アカウントの削除申請」を受け付ける機能がありました。

しかし以前の設計では、「ユーザーが BAN される前にブラウザで取得していた古い Firebase トークン」 がそのまま使えてしまったり、認証失敗時にフォールバックする抜け道が存在していました。

そこで、BAN 済みユーザーからの申請経路を Google OAuth ID token による再認証の単一経路 に完全一本化しました。

【 削除申請の単一再認証パイプライン 】
ユーザーが削除申請ボタンを押下

Google Sign-In で最新の Google ID token をその場で取得(※ Web Storage には一切保存しない)

バックエンドで 5 つの項目を厳格検証 ⚡️
  1. トークン署名(Google 公開鍵)
  2. audience(クライアント ID)
  3. issuer(accounts.google.com)
  4. 有効期限(exp)
  5. email_verified == true

検証済み email から Firebase UID をサーバー側で安全に解決

申請を受け付ける(※ 1 つでも検証に失敗したら即座に拒絶)

旧エンドポイントや古い Firebase トークンへの依存をすべて撤去し、「最新の Google ID token による厳格な再認証を通過しなければ 1 ステップも進めない」 という確固たる契約を敷きました。

3. 推移的依存の最小侵襲パッチと CI 出力のサニタイズ

推移的依存の脆弱性対応

npm audit で推移的依存(依存ライブラリが内部で依存しているパッケージ)に Critical な脆弱性が検知されました。

アプリケーションコードから直接使われていない内部パスであっても、本番イメージの依存グラフに含まれている以上、放置は禁物です。 無理に overrides や直接依存を追加して package.json の結合を増やすのではなく、親パッケージの semver 許容範囲内で安全にパッチ版へ引き上げる最小侵襲の更新 を行いました。

Terraform CI 出力のフェイルクローズ化

GitHub Actions で terraform plan が失敗した際、以前はエラー内容を把握しやすくするために stdoutstderr の生出力をそのまま PR のステータスコメントに転記していました。

しかし、PR の差分内容によっては 内部の Secret やインフラの機密情報が生ログに含まれて PR 上に露出してしまうリスク があります。

【 CI 出力のサニタイズ設計 】
Terraform plan が失敗

PR コメントには生の stderr を出さず「固定ステータス」のみを表示 🛡️

詳細な失敗原因は、Cloud Audit Logs を固定の filter で引いて取得

許可された 4 項目(タイムスタンプ・プリンシパル・メソッド・エラーコード)だけを整形して表示

失敗診断の利便性を保ちつつ、機密情報の露出を構造的に遮断するフェイルクローズな CI パイプラインに改修しました。

まとめ: セキュリティは「当たり前」を確実に塞ぎ切ること

今回実施した 4 つの対応は、どれも最先端の技術を使っているわけではありません。

  1. 外界との境界(pre-push)で secretlint を走らせる
  2. BAN 後の操作を Google OAuth の最新トークン再認証に縛る
  3. 推移的依存の脆弱性を最小侵襲で解消する
  4. CI の生ログを PR に垂れ流さず、監査ログの最小項目に倒す

「やれば確実に状態が良くなると分かっていること」を、言い訳をせずに 1 つずつ確実に塞ぎ切る。この地道なガードレールの積み重ねこそが、システムを最も堅牢に保つ秘訣です。

参考リンク