Brakeman(静的解析)にignoreの理由を書くことを促すActionを自作してみました
こんにちは。ダイの大冒険ガチ勢のbun913と申します。
みなさん静的解析を行っていますか?TypeScriptやJavaScriptなどの言語ではESLintやTSLint、RubyではRuboCopなどが有名ですね。
Ruby向けの静的解析ツールとしては脆弱性のあるコードの検出を試みるBrakemanもあります。
ただし、静的解析はあくまで実行時の挙動を見るものではないので、すべての警告が必ずしも問題とは限らず、偽陽性(本当は問題ないのに警告が出る)と判断することがあります。
とはいえ、無視設定が溜まっていき、その理由がわからなくなることや、後で修正するつもりだったけど忘れてしまうこともありますよね。
そこで、今回はBrakemanの警告に対して、無視する理由を記述することを促すGitHub Actionsを自作してみました。
作ってみたツール
以下のリポジトリにて公開しています。
以下のようにGitHub Actionsで呼び出すことで、指定したBrakemanのignoreファイルに対して、無視理由の記述を促します。
jobs:
lint-workflow:
name: Check dist/
runs-on: ubuntu-latest
steps:
- name: Checkout
id: checkout
uses: actions/checkout@v4
- name: Brakeman ignore reason check
id: valid-json
# バージョンは最新のものを指定してください
uses: bun913/please-write-ignore-reason@v0.9.2
with:
# ↓ 一応複数のファイルを指定できるようにしています
fileListString: samples/brakeman.ignore,samples/valid2.json
以下のように、指定したファイルにignoreの理由が書かれていなかったり、存在しないファイルを指定するとエラーが出力されます。
ざっくりとした仕組み
このツールの挙動は非常にシンプルで、以下のようなbrakeman.ignore
のファイルに対して、ignore_warnings
の配下の要素のnote
プロパティが5文字未満であったり、ファイルが存在しない場合にエラーを出力します。
{
"ignored_warnings": [
{
"warning_type": "Remote Code Execution",
"warning_code": 25,
"fingerprint": "006ac5fe3834bf2e73ee51b67eb111066f618be46e391d493c541ea2a906a82f",
"check_name": "Deserialize",
"message": "`Oj.load` called with parameter value",
"file": "app/controllers/users_controller.rb",
"line": 52,
"link": "https://brakemanscanner.org/docs/warning_types/unsafe_deserialization",
"code": "Oj.load(params[:json], :mode => :object)",
"render_path": null,
"location": {
"type": "method",
"class": "UsersController",
"method": "some_api"
},
"user_input": "params[:json]",
"confidence": "High",
// ↓5文字未満なのでエラーになります
"note": ""
},
{
"warning_type": "Remote Code Execution",
"warning_code": 25,
"fingerprint": "3bc375c9cb79d8bcd9e7f1c09a574fa3deeab17f924cf20455cbd4c15e9c66eb",
"check_name": "Deserialize",
"message": "`Oj.object_load` called with parameter value",
"file": "app/controllers/users_controller.rb",
"line": 53,
"link": "https://brakemanscanner.org/docs/warning_types/unsafe_deserialization",
"code": "Oj.object_load(params[:json], :mode => :strict)",
"render_path": null,
"location": {
"type": "method",
"class": "UsersController",
"method": "some_api"
},
"user_input": "params[:json]",
"confidence": "High",
// 5文字未満なのでエラーになります
"note": "a"
},
{
"warning_type": "Remote Code Execution",
"warning_code": 25,
"fingerprint": "97ecaa5677c8eadaed09217a704e59092921fab24cc751e05dfb7b167beda2cf",
"check_name": "Deserialize",
"message": "`Oj.load` called with parameter value",
"file": "app/controllers/users_controller.rb",
"line": 51,
"link": "https://brakemanscanner.org/docs/warning_types/unsafe_deserialization",
"code": "Oj.load(params[:json])",
"render_path": null,
"location": {
"type": "method",
"class": "UsersController",
"method": "some_api"
},
"user_input": "params[:json]",
"confidence": "High",
// ↓OKです
"note": "Ref: https://example.com/issue/45"
}
],
"updated": "2019-04-02 12:15:05 -0700",
"brakeman_version": "4.5.0"
}
処理自体はTypeScriptで記述しており、バリデーションの実装はValibotというライブラリでサクッと実装しています。
JSONファイルからパースする際に、ValibotによりJSONデータの型が自動的に検証されるため、型の安全性を確保しやすいです。(as
で型アサーションするだけだと、型チェックとしては不十分ですので)
GitHub Actions の Action を TypeScript で作る際は、以下の Public Template を利用するとスムーズに作成できます。使い方は、README に書かれおり非常にわかりやすいです。
今後の展望・感想
今後やりたいこと
- GitHub Actions だけではなく CircleCI などで利用できるようにしたい
- 以下のスクラップでは、daggerというツールを使って、CircleCIなどでも利用できるようにしようとした形跡を書いています
- 今後も楽に書き続けることを考えて、断念しました。リアルの息吹を感じますね?
- daggerを使ってGitHub Actionsでも使えるCIを書いてみるまでの道のり
- Brakeman だけでなく、他の静的解析ツールにも対応をしたい(かもしれません)
- ルールに柔軟性を持たせる
- 強制的に5文字以上の理由としてますが、緩すぎる気もしますし、微妙な気もします。
-
Ref:#45
のようなリンクを貼ることを考えて、ざっくりと5文字と設定しました
-
- 強制的に5文字以上の理由としてますが、緩すぎる気もしますし、微妙な気もします。
他にも生成AIを使って理由の正当性チェックが可能かもしれませんが、現在はそこまでの機能は不要と考えています。
あくまでも、うっかりミスの予防や、チームで既存の無視理由を考えるためのツールとしての利用を想定しています。
感想
何より、今回は数時間でサクサクと作って公開することが目的でしたので、かなりシンプルな実装になっています。
公開さえすれば親切な方がより良い形にしてくれるかもしれませんので、Better than Nothingの精神でやってしまいました。
かなりニッチなツールなのですが、使いやすいように改良をしていきたいと思います。
以上、最後までお読みいただきありがとうございました。
Discussion