あひるの勉強部屋

つらつらつら~と不定期にカキコするブログ

brakeman+reviewdogの実行環境による出力差異を調べた

Railsプロジェクトにてbrakemanの実行結果をreviewdogでフィルタしようと動作確認していたところ、brakemanの実行環境によって差が出たので書き残します。
ちなみに結論の概要としては、brakemanの出力するファイルパスが絶対パスであることが原因でした。

前提

使用ツール

brakeman

Railsプロジェクトのセキュリティ脆弱性を静的解析してくれる。
GitHub - presidentbeef/brakeman: A static analysis security vulnerability scanner for Ruby on Rails applications

reviewdog

linterなどの出力をコード差分についてアクション実行してくれる。
GitHub - reviewdog/reviewdog: 🐶 Automated code review tool integrated with any code analysis tools regardless of programming language

実行環境

Railsプロジェクトは基本的にdocker composeを用いたDocker環境上で実行されている。
reviewdogはホスト上にのみインストールされている。

導入背景

先日開催されたKaigi on Rails 2024のActiveRecord SQLインジェクションクイズを観ていて exists? のインジェクションに全く気づきませんでした。(クイズの内容面白かったです) kaigionrails.org

これに若干の危機感を抱き、人によるレビューだけでなく機械的に検出できるようにするべきと思い立ち、brakemanをCIで実行するように設定すべく手元マシンの環境で試し始めました。

やってたこと

breakeman の実行結果を確認ということで、 Sample.exists?(params[:id]) なコードを埋め込み実行結果の確認をしました。

$ docker compose run --rm app brakeman -q --format tabs
/myapp/app/controllers/application_controller.rb        5       SQL Injection   General Possible SQL injection near line 5: Sample.exists?(params[:id]) High

はい、ちゃんと出ていますね。 これをreviewdogでフィルタしていきます。

$ docker compose run --rm app brakeman -q --format tabs | reviewdog -reporter=local -f=brakeman -diff="git diff main"

何も出てきません。 gitの差分としては当該差分が存在するため検出できないことはないはずです。 試しにホスト上で実行してみると。

$ docker compose run --rm app brakeman -q --format tabs | reviewdog -reporter=local -f=brakeman -diff="git diff main"
/Users/username/tmp/app/controllers/application_controller.rb        5       SQL Injection   General Possible SQL injection near line 5: Sample.exists?(params[:id]) High

出力されました。
ここからttyの問題とかあるんだろうかとか色々と悩んだのですが、結論としては冒頭に書いた通りパスの違いが原因と気づきました。

# docker実行時
/myapp/app/controllers/application_controller.rb
# ホスト実行時
/Users/username/tmp/app/controllers/application_controller.rb

並べてみるとあからさまに違いますし、後からみるとなぜ気づかなかったのかという気持ちになります。(絶対パスには目が行かず脳内で勝手に相対パスに変換されていました)

要はreviewdogはホスト上で実行しているため、ホストでの絶対パスは解決できるがdockerコンテナのマウントパスを渡されてもホスト上に見当たらないためフィルタ結果が空になっていたという感じのようです。
日頃rubocopなどもdocker環境で実行してreviewdogでフィルタしてるのに動いていたのは相対パスだったからなのだなという気づきも得られました。

未確認の疑問

今回はreviewdogと連携するために --format tabs オプションで実行しましたが、実はこのオプションなしの出力の場合はファイルパスが相対パスとなります。

report_tabsの実装を追ってみたところ、absolute が明示されています。
brakeman/lib/brakeman/report/report_tabs.rb at v6.2.2 · presidentbeef/brakeman · GitHub

一方でOPTIONS.mdの方では下記の通りデフォルトでは相対パスで出力されるため絶対パスでの出力には --absolute-paths オプションを使うようにと記載があります。
この記載を見る限りでは --format tabs でもオプション未指定の場合は相対パスで出力されるべきな気がしています。

Reports show relative paths by default. To use absolute paths instead:

brakeman/OPTIONS.md at v6.2.2 · presidentbeef/brakeman · GitHub

ドキュメントと実装があべこべのため対応漏れの可能性を考えましたが、report_tabsの実装のコメントにJenkins Brakeman Pluginに合うように調整してあると書かれているので、もしかしたら意図的なのかもしれません。
このあたりについてはJenkins Brakeman Pluginの実装を確認して、問題がなければ相対パスを返すように対応入れるのもありそうだなと考えています。

[追記: 2024/11/10] Jenkins Brakeman Pluginは2020にリポジトリアーカイブされているし、考慮の必要はないかもしれない

まとめ

実行環境後とのパスの表記の確認はしよう。

(ちなみにreport_tabsの absoluterelative にしてみたところ相対パスで出力され、ホストとdocker両方でreviewdogによるフィルタが正常に動作することは確認しました)