Rebounder Tech Blog

運用している当事者が書く、本番システムの記録。

配布パッケージの漏洩検査は、grepの対象パスがパッケージの半分しか見ていなかった

公開 読了時間 約4分執筆: Rebounder 開発チーム(当該システムの運用当事者)

※本記事にはアフィリエイトリンクを含む場合があります。内容は広告の有無に影響されません。

結論

配布物の漏洩検査を grep の固定ワードリストで作ると、語彙をどれだけ増やしても、検査対象のパスがパッケージ全体を見ていなければ素通りする。実際に起きたのは語彙不足ではなく、スキル本体の配下しか grep していなかったという範囲の穴だった。

結論

配布用パッケージの漏洩検査を grep の固定ワードリストで作っていたが、語彙をいくら増やしても防げない穴があった。検査コマンドが grep していたパスが、パッケージの一部でしかなかったからだ。

  • インストーラスクリプトには、社内向けの注意書きがそのまま残って買い手に届いていた
  • 利用条件として同梱していたファイルは、法務レビューを通す前の下書き段階のものだった
  • どちらも grep 検査は毎回 pass していた。検査していたディレクトリの外にあったため

語彙の追加だけでは再発を防げないと判断し、検査対象のパス自体を見直した。

症状

社内のスキル集を有償パッケージとして配布するビルドスクリプトを運用していた。rsync で成果物を出力ディレクトリへ集め、grep で社内固有の語(社名・個人名・ローカルパスの断片など)が残っていないか検査してから zip にする、という構成だ。検査は毎回グリーンで、CI 的には「社内固有の記述は無い」という結果しか見えなかった。

ところが買い手からのレビューで、次の2点を指摘された。

  • 同梱したインストーラスクリプトの中に、「このリポジトリは社内の移植元であって、購入者が常用する想定のものではない」という趣旨の社内向けコメントがそのまま出力されていた
  • 「利用条件」として同梱したファイルが、社内で法務レビューにかける前の下書き段階の文書だった

どちらも文字列としては実在し、意味も通っている。問題は、grep がこれらのファイルを一度も検査していなかったことだ。

原因

検査コマンドを見直すと、grep の対象パスが出力ディレクトリ全体ではなく、スキル本体を格納するサブディレクトリだけになっていた。

一方で、インストーラスクリプトと「利用条件」ファイルは、そのサブディレクトリの外——出力ディレクトリの直下にコピーされる構成だった。grep の対象パスに、それらのファイルは最初から含まれていなかった。

社内限定の語をどれだけ検査語リストに積んでも、grep が読みに行くパスの外にあるファイルには一致しようがない。今回の指摘を受けて語彙自体の不足(法務レビュー前段階を示す語や、社内の別プロジェクトを指す固有の呼称が未登録だった)も同時に見つかったが、それを直すだけでは同じ穴が残ったままだった。検査対象のパスが半分しか見ていない以上、語彙をどれだけ充実させても、パスの外にあるファイルは永久に検査されない。

直す

2段構えにした。

1. grep の対象パスを、出力ディレクトリの直下も含めた全体に広げる。

# 修正前: スキル本体のサブディレクトリだけを検査していた
grep -rInE "$WORDS" "$OUT/skills/"

# 修正後: 直下にコピーしたインストーラや同梱文書も含め、パッケージ全体を対象にする
grep -rInE "$WORDS" "$OUT" --include="*.md" --include="*.sh"

2. grep の前段に「洗浄」の工程を追加する。

grep は「検査語リストに載っている語だけを見つける」仕組みなので、リストに無い新しい漏洩パターンには無力だ。個別の語を追いかけ続ける代わりに、配布時に必ず落とすべきカテゴリを機械的に処理する工程を先に置いた。出自を示すコメント(どのリポジトリ由来かを示す記述)や、社内環境専用の設定値を、sed で配布直前に書き換える。

for f in "$OUT/install.sh" "$OUT/uninstall.sh"; do
  sed -i '' '/<社内リポジトリを指すパターン>/d' "$f"
  sed -i '' 's/<社内環境専用の設定値>/<配布用の既定値>/' "$f"
done
bash -n "$OUT/install.sh" && bash -n "$OUT/uninstall.sh" \
  || { echo "洗浄でスクリプトが壊れた"; exit 1; }

sed による書き換えはスクリプトの構文を壊しかねないので、洗浄した直後に bash -n で構文チェックを挟んでいる。洗浄で機械的に落とし、grep は最後の砦として全体を検査する、という順序にした。

再発しにくい形

denylist 型の検査は、語彙を追加するたびに「これで防げた」という安心感を生みやすい。だが今回の実態は、語彙ではなくパスの方が半分しか見ていなかったというものだった。検査が毎回グリーンで返ってくること自体が、「検査対象が正しい」ことの証明にはならない。

見直すべき順番は逆にした方がいい。まず検査コマンドが実際に何を読みに行っているかを疑い、それがパッケージ全体をカバーしているかを確認してから、語彙の充実に取り掛かる。今回のように出力先のディレクトリ構成が途中で変わる(直下にコピーするファイルが増える)場面では、検査範囲の見直しが語彙の更新より後回しになりやすい。

同じように「検査対象の想定と実態がずれる」話は環境ごとに変わるはずのデフォルト値が環境をまたいで漏れた事故にもある。どちらも、検査そのものは動いているのに、見ている範囲が思っていたより狭かったという構図だ。

よくある質問

Q1grepの検査語リストを増やせば防げたのですか?

増やすだけでは防げませんでした。修正前の検査コマンドはパッケージ直下にコピーしたインストーラや利用条件ファイルではなく、スキル本体を格納したサブディレクトリだけを grep していました。語彙が完璧でも、検査対象のパスがそこに含まれていなければ結果は同じく素通りします。

Q2なぜ買い手のレビューで気づくまで社内で気づけなかったのですか?

ビルドスクリプトの grep 検査は毎回 exit 0 で通っていたため、CI 的には『検査済みで問題なし』という結果しか見えませんでした。検査対象のパスが半分しか見ていないという事実は、実際に混入したものを人間が読んで比較しない限り、コマンドの終了コードからは分かりません。

Q3語彙リストへの追記だけでなく、他に何を変えたのですか?

grep の対象をパッケージ直下も含めた全体に広げたのに加えて、grep の前段に『洗浄』の工程を追加しました。配布時にだけ機械的に書き換える sed のルールを用意し、出自を示すコメントや社内環境専用の設定値をあらかじめ削ぎ落としてから、最後の砦として grep 検査を通す二段構えにしています。洗浄後は bash -n でスクリプトの構文が壊れていないかも検査します。

Q4この構成は自分たちのビルドスクリプト以外にも当てはまりますか?

はい。denylist 型の検査全般に当てはまります。検査対象のパスを一度決めて安心してしまうと、その後にファイルの置き場所が変わったり、新しい出力ファイルが増えたりしたときに、検査範囲の見直しが追いつきません。語彙の充実よりも先に、検査が実際に何を読んでいるかを疑うべきです。

確認した環境

  • bash 3.2 / macOS 標準の BSD sed(sed -i '')
  • 2026-08-14、買い手レビュー12件・運用監査15件への対応としてビルドスクリプトを修正

この記事の根拠

  • シェルスクリプトファイル 31〜37行目コミット dd5f794
  • シェルスクリプトファイル 24〜28行目コミット dd5f794
  • シェルスクリプトファイル 21〜27行目コミット cc42262
  • シェルスクリプトファイル 31〜35行目コミット cc42262

本文の主張は、上の記録に書かれていることだけです。運用しているリポジトリは非公開のため リンクは張れませんが、どのファイルの何行目を、どのコミット時点で見て書いたかは 記事ごとに残しています。推測で書いた箇所はありません。