GitHub CodeQLが個人アカウントでは使えなかった話 ― 開発ツールを9本まとめて入れた棚卸しの顛末
個人開発しているMinecraftサーバー監視アプリ「MineWatch」(公式サイト、アプリの全体像はこちら)で、ある日「未導入のツールが9個ある」と気づいて、まとめて導入したことがあります。この記事は、その顛末と、導入したツールが実際に見つけた本物のバグ、そして「入れたくても入れられなかった」ツールの話です。
TL;DR
- RCON機能のPRレビューをきっかけに、リポジトリ横断でツールチェーンを棚卸ししたところ、gitleaks・Trivy・ty・Periphery・Semgrep・Renovate・kube-linter/checkov・Spectral・npm audit/pip-auditの9個が未導入だと分かった。
- 型チェッカー(ty)を初めて実行したら、既存コードに実際のバグ(Python 3.11以降でしか使えない
asyncio.timeout()を、requires-python>=3.10のまま使っていた)が見つかった。CIがPython 3.13でしかテストしていなかったため見逃されていた。 - Swiftの未使用コード検出ツール(Periphery)を入れたら、削除し忘れたファイルと、Jenkinsの永続workspaceに残った古い
.xcodeprojのせいでビルド設定が更新されないMakefileの不具合の、2つが見つかった。 - GitHub CodeQLを検討したが、個人アカウントのprivateリポジトリでは購入メニュー自体が存在せず、導入できなかった。 代替として、Semgrepの無料フォークであるOpenGrepへ移行した。
- 9個全部を同じ熱量でCIに常時組み込んだわけではない。Mutation testingのように、あえて「オンデマンド実行のみ、CIには入れない」という判断をしたツールもある。
1. きっかけ: RCON機能のレビューで気づいた棚卸し漏れ
RCON(Minecraftサーバーへのリモートコマンド実行)機能を実装していたセッション中、~/.netrcのトークンを誤って表示してしまう事故がありました。実害はなかったものの、これをきっかけにリポジトリ横断で既存のツールチェーンを棚卸ししたところ、次の9個が未導入だと分かりました。
| # | ツール | 目的 |
|---|---|---|
| 1 | gitleaks | secret検出(コミット前) |
| 2 | Trivy | Dockerイメージ脆弱性スキャン |
| 3 | ty | Python型チェック(pyproject.tomlに設定はあったが未実行) |
| 4 | Periphery | Swift未使用コード検出 |
| 5 | Semgrep | SAST/パターンベース検出 |
| 6 | Renovate | 依存関係の自動更新PR |
| 7 | kube-linter/checkov | k8sマニフェストのlint |
| 8 | Spectral | OpenAPI specのlint |
| 9 | npm audit / pip-audit | 依存の既知脆弱性チェック |
対象はMineWatch本体だけでなく、関連する3つの個人リポジトリ(iOSの共通ライブラリ、自作のRCONクライアント、CIパイプライン定義)にもまたがりました。優先度は「secret検出とイメージ脆弱性スキャン(高)」「型チェック・未使用コード・SAST(中)」「マニフェストlintとOpenAPI lint・依存監査(低)」の3段階に分け、2日ほどで全9個を導入しました。
2. 型チェッカー(ty)が実際のバグを見つけた
pyproject.tomlにはtyの設定がすでにありましたが、実行フローには組み込まれていませんでした。Makefileにtypecheckターゲットを足し、pre-commitにもPythonのフックとして追加して初めて実行したところ、既存コードに152件の診断が出ました。ほとんどは型ヒントの補完で済みましたが、そのうちの1件は実際のバグでした。
RCONクライアント側(自作の別リポジトリ)のコードが、requires-python = ">=3.10"と宣言しているにもかかわらず、Python 3.11で追加されたasyncio.timeout()を使っていました。CIがPython 3.13だけでテストしていたため、3.10環境での非互換が今まで一度も検出されていませんでした。tyの指摘をきっかけにrequires-pythonを">=3.11"へ修正しています。
型チェッカーを「新規に有効化した」だけで、書いたコードは1行も変えていないのに、実際に動かなくなりうる環境依存のバグが見つかった、という点がこの導入で一番印象に残っています。
3. Peripheryで見つけた2つの罠
Swiftの未使用コード検出ツールPeripheryも、導入した瞬間に2つの副産物がありました。
1つ目は、素直な未使用コードです。AppEnvironment.swiftへ置き換わった後に消し忘れていた古いAppConfig.swift(参照ゼロ)が見つかり、削除しました。
2つ目は、もう少し厄介でした。Makefileのビルド・テスト・未使用コード検出の各ターゲットが、.xcodeprojファイルの存在だけを見て「生成済みかどうか」を判定していました。Jenkinsのエージェントは永続化されたworkspaceを使い回すため、Swiftファイルを削除しても、既存の.xcodeprojファイルはそのまま残ります。その結果、ビルド設定の再生成(make swift-init)が走らず、CI上ではファイル削除が反映されないままの、古いプロジェクト構成でビルド・テストが実行され続ける不具合になっていました。対応として、各ターゲットの依存を「ファイルの存在」ではなくswift-init自体(.PHONY)に変更しています。
Peripheryはビルドを要するため実行が重く、pre-commitには入れずJenkins側の独立ステージにしました。もう1つ気づいたのは、共通ライブラリ側(SwiftPMのライブラリとして配布している別リポジトリ)ではretain_public: trueの設定が必須だったことです。これが無いと、ライブラリが外部に公開しているAPI全体が「(ライブラリ内から見ると)未使用」として誤検知されてしまいます。
4. GitHub CodeQLは個人アカウントでは使えなかった
Semgrepとは異なる検出方式(データフロー解析)を追加する目的で、GitHub CodeQLの導入を検討しました。調査した結果は次の通りです。
CodeQLの実体であるGitHub Advanced Security(2025年以降「GitHub Code Security」に再編)は、GitHub TeamまたはEnterpriseという組織(Organization)向けプランの有料アドオンとしてのみ提供される。個人アカウント(Free)のプライベートリポジトリには購入メニュー自体が存在せず、
github/codeql-actionをworkflowに追加しても、リポジトリ設定の「Code security and analysis」のEnableボタンが個人アカウントのプライベートリポジトリでは表示されない(パブリックリポジトリなら誰でも無料)。
対象言語(TypeScript/JavaScript・Python・Swift)はどれもCodeQL公式サポート対象で、技術的な障壁は無かったのに、契約形態(個人アカウントのprivateリポジトリ向けプランが存在しない)だけがボトルネックでした。ツールの機能を比較する以前に、そもそも「買えない」という結論です。
5. 代替: OpenGrepへ移行
CodeQLが使えないので、代わりの選択肢を調べました。
-
OpenGrep(最有力): Semgrepが一部機能を有料化した後にできた、LGPLライセンスの無料フォーク。Semgrepが有料化したtaint解析・関数をまたいだ追跡(
--taint-intrafile、同一ファイル内)を無料で使える。既存のSemgrepルール資産をそのまま流用できるため移行コストが低い。ファイルをまたぐ追跡は開発中とのこと。 - Semgrep AppSec Platform無料枠: 10コントリビューター・10プライベートリポジトリまで無料で、Pro Engine(cross-file/cross-functionのデータフロー解析)が使える。
最終的にOpenGrepへの移行を選び、既存のPro向けpre-commitフックを置き換えました。CIのステップ名もopengrep (p/ci, taint-intrafile)に変わっています。Semgrep AppSec Platformの無料枠は、必要になれば別途検討する扱いのまま、今のところは着手していません。
「有料機能が使えなくて困った」で終わらず、無料の代替(しかも既存資産をそのまま流用できるフォーク)が実際に存在していた、というのは幸運でした。
6. 残りのツールは簡潔に
- gitleaks: 全コミット履歴を検証し、漏洩は無し。Firebase Web SDKの設定値のような「意図的に非秘密として扱っている値」は、誤検知防止のためallowlistに登録した。
-
Trivy: 導入した時点でOSパッケージ由来のCRITICAL/HIGH脆弱性が実際に存在していたため、対象イメージのDockerfileに
apt-get upgrade/apk upgradeを追加してあわせて解消した。 -
Semgrep(後にOpenGrepへ移行): 導入時、GitHub Actionsのworkflowで
github.refをrun:ステップへ直接埋め込んでいたことによるシェルインジェクションの実害ある指摘があり、3リポジトリ分まとめて修正した。 - Renovate: uv/npm/GitHub Actionsの3エコシステムを対象に導入。non-majorはグルーピング、major・脆弱性は個別扱いにして、毎週月曜早朝(JST)に実行するよう設定した(CIのビルドノードがMac1台構成のため、負荷を抑える目的)。
-
kube-linter/checkov: 導入時点の61件のうち47件を実修正(
imagePullPolicy・seccompProfile・capabilities drop・readOnlyRootFilesystem等)。副産物として、本番のnginxのanti-affinity設定不足と、Celery beatの読み取り専用起動の不具合も見つかった。 -
Spectral(OpenAPI仕様のlint): 導入時点の44件のうち43件を実修正(FastAPI初期化への
description・openapi_tags追加)。 - npm audit / pip-audit: 導入の過程で、Next.jsのCRITICAL脆弱性を検出し、対象バージョンへアップグレードして解消した。
7. 全部を同じ熱量でCIに入れたわけではない
ツール導入が一段落した後、さらに拡充を検討した項目が3つありました。
-
Renovateの
minimumReleaseAge整備:.npmrc側にあった「公開直後(7日未満)のパッケージは新規解決しない」というポリシー(アカウント乗っ取りによる悪意あるバージョン公開への対策)が、Renovate側の設定には反映されていなかった。実際に、脆弱性修正版パッケージが公開2日後だったためこのポリシーに阻まれてインストールできなかった実例があり、Renovate側にも同じ待機時間(7日)を設定して揃えた。 -
Mutation testing(mutmut/StrykerJS): カバレッジの数値だけでは分からない「テストが実際にバグを検出できるか」を確かめる仕組みを調査した。導入自体はしたが、変異数×通常のテスト実行時間で、フルスキャンが通常のテストスイートの数十倍以上に膨らむため、CIへの常時組み込みは見送り、
make mutation系のターゲットからオンデマンドで手元実行するだけにした。 - アクセシビリティ自動チェック(@axe-core/playwright): 既にE2EテストでPlaywrightを使っていたため、新規ツール導入ではなく既存のPlaywrightへの機能追加として導入した。副産物としてカラーコントラスト違反を5件検出・修正している。axeで検出できるのはWCAG違反の3〜4割程度とのことで、体系的なチェックの入り口にはなったが、手動確認を置き換えるものではない。
ツールを入れること自体が目的化しないよう、「実行コストに見合うか」「常時実行すべきか、スポットでよいか」を毎回別々に判断しました。
まとめ
- 型チェッカーもコード未使用検出も、「新規に有効化しただけ」で実際のバグが見つかった。ルールベースの静的解析は、既存コードに長く手を入れていないほど、後から入れたときの検出量が多くなる。
- ツールを検討する過程で「使いたくても契約上使えない」ケース(CodeQL)に当たった。機能の比較をする前に、まず契約形態(個人アカウント向けプランの有無)を確認すべきだった。
- 導入できないと分かって終わりにせず、代替(OpenGrep)を探したことで、当初の目的(taint解析の強化)自体は達成できた。
- 9個すべてを同じ強度でCIに常時組み込んだわけではない。実行コストの大きいツールは、常時実行とスポット実行を意識的に使い分けた。
個人開発の環境での構成なので、そのまま組織に持ち込む場合は、有料プランの契約状況やライセンス(OpenGrepのLGPL等)を先に確認してください。
JQITのエンジニアの95%以上は未経験からの採用です。
よければコーポレートサイトにも遊びに来てください。
未経験から学べます!一緒に挑戦していきましょう![]()
noteやXもやってます↓