Rebounder Tech Blog

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

semgrep ci --config=autoでコード変更なしにmainが赤くなる

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

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

結論

semgrep ci --config=autoはSemgrepレジストリのコミュニティ規則を実行のたびに取得するため、リポジトリを一切変更しなくても上流が規則を追加した日だけmainブランチが赤くなる。

結論

**semgrep ci --config=autoはSemgrepレジストリのコミュニティ規則(実測1074件)を実行のたびに取得するため、リポジトリのコードを一切変更していなくても、上流が新しい規則を追加した日だけCIのmainブランチが赤くなる。**旧構成ではSEMGREP_RULES環境変数で5つのルールセットを宣言していたが、実行コマンド自体には--config=autoを渡していたためその宣言は読まれず、常にレジストリ全件が走っていた。対処は規則セットを--config=p/xxxで明示列挙し、Semgrepのコンテナイメージをdigestで固定することだった。

症状

キミテラス-v2のsecurity.ymlはSAST(静的解析)にSemgrepを使っている。修正前(abb0ba6時点)の構成は次の形だった。

semgrep:
  name: SAST (Semgrep)
  runs-on: ubuntu-latest
  container:
    image: semgrep/semgrep
  steps:
    - uses: actions/checkout@v6
    - run: semgrep ci --config=auto --sarif --output=semgrep.sarif
      env:
        SEMGREP_RULES: >-
          p/typescript
          p/javascript
          p/nodejs
          p/owasp-top-ten
          p/security-audit

一見、SEMGREP_RULESで5つのルールセットに絞っているように見える。だがsemgrep ciの実行コマンド自体に渡しているのは--config=autoで、--config=autoが指定されるとSemgrepはSemgrepレジストリのコミュニティ規則を都度取得して実行する(実測1074件)。SEMGREP_RULES環境変数の宣言はここでは読まれない。コンテナイメージもsemgrep/semgrepとタグ無し(=latest)のまま指定されていた。

2026-07、Semgrepレジストリにsupply-chain系の新しい規則が追加され、それだけで41件がblockingとして検出されmainが恒常的に赤くなった。リポジトリのコードは一行も変わっていない。

原因

原因は2つ重なっている。

  1. --config=autoがSEMGREP_RULESの宣言を無効化していた。 宣言した5つのルールセットは実際には走っておらず、常にレジストリ全件(1074件)が対象になっていた。宣言と実挙動が乖離した状態が続いていた。
  2. Semgrep OSSには重大度フィルタが無い。 検出されたものは全件blockingとして扱われるため、ゲートの合否は「どの規則を走らせるか」だけで決まる。--config=autoはSemgrepレジストリの中身に依存するため、レジストリ側が規則を追加・変更した瞬間にゲートの合否が変わりうる。コンテナイメージもタグ無し(latest)だったため、Semgrep自体のバージョンアップでも挙動が変わりうる状態だった。

この2つの結果、「リポジトリを一切変更しなくても上流が規則を足した日にmainが赤くなる」という、コードレビューでは検知できない構造になっていた。

直す

対処は3段になっている。

① 規則セットを明示列挙し、エンジンをdigestで固定する。

semgrep:
  name: SAST (Semgrep)
  runs-on: ubuntu-latest
  container:
    image: semgrep/semgrep@sha256:bdf7013b2c3634a487671158da77c554f531742326b543a9464d2adf6c433ac8 # 1.171.0
  steps:
    - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
    - run: >-
        semgrep ci --sarif --output=semgrep.sarif
        --config=p/typescript
        --config=p/javascript
        --config=p/nodejs
        --config=p/owasp-top-ten
        --config=p/security-audit
        --config=p/github-actions
        --exclude-rule=package_managers.pnpm.pnpm-missing-minimum-release-age.pnpm-minimum-release-age
        --exclude-rule=package_managers.pnpm.pnpm-block-exotic-sub-dependencies.pnpm-block-exotic-sub-dependencies
        --exclude-rule=package_managers.pnpm.pnpm-trust-policy.pnpm-trust-policy
        --exclude-rule=terraform.gcp.security.gcp-cloud-storage-logging.gcp-cloud-storage-logging

--config=autoは消え、代わりに--config=p/xxxを6つ明示している。コンテナイメージもlatestではなくdigest固定に変えた。p/github-actionsを新たに加えているのは、uses:のSHA pinが退行していないかを継続的に検知するため。

--exclude-ruleで外している4件には、それぞれ理由がコードコメントとして残っている。pnpm-minimum-release-ageとpnpm-block-exotic-sub-dependenciesは「pnpm 11の既定で既に有効な設定への誤検知」、pnpm-trust-policyは「--frozen-lockfileでも既存ロック全件に再適用され、上流都合だけでリポジトリ無変更のままCIが落ちうるため意図的に不採用」、terraformの1件は「実指摘だが対応は別issueで追跡中」——除外は無言で足さず、理由を書いた上で行われている。

② --config=autoは週次限定の非blockingジョブに分離する。

semgrep-discovery:
  name: SAST Discovery (Semgrep auto, non-blocking)
  runs-on: ubuntu-latest
  if: github.event_name == 'schedule'
  continue-on-error: true
  container:
    image: semgrep/semgrep@sha256:bdf7013b2c3634a487671158da77c554f531742326b543a9464d2adf6c433ac8 # 1.171.0
  steps:
    - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
    - run: semgrep ci --config=auto --sarif --output=semgrep-auto.sarif
      continue-on-error: true

--config=autoの広い網自体は捨てていない。ただしblocking判定からは外し、週1回のscheduleだけで動く非blockingジョブに移した。ここでの検出はmainを止めず、code scanningアラートとして残り続ける形にした。

③ uses:をSHA pinする。 同じコミットで.github/workflows配下のuses:を合計35箇所(security.yml13箇所+ci.yml22箇所)、40桁SHA+バージョンコメントへ変更している。actions/dependency-review-action@v5はタグではなくブランチ参照だったため、他のタグ参照より更に可変性が高かった。

再発防止

この構造は「規則セットを絞ったつもりでも、実行コマンドの他のフラグに上書きされていないか」を確認しないと再発する。envでルールセットを宣言する形はSemgrepの--config=auto実行時には読まれず、絞り込みは--configフラグとして渡す必要がある。また重大度フィルタの無いツールをblockingゲートに使う場合、規則セットの取得元(autoのようにレジストリへ都度アクセスするか、p/xxxのように固定セットを指定するか)自体が、コードを変更しなくてもゲートの合否を左右する変数になる。p/xxxを明示列挙しても完全な決定論にはならない(scan時にレジストリから内容を取得する点はautoと同じ)ことを踏まえたうえで、「どこまでをblockingで許容し、どこからをdiscoveryへ逃がすか」をあらかじめ線引きしておくと、同じ形の恒常赤化を避けやすい。

よくある質問

Q1なぜコードを変更していないのにmainが赤くなったのですか?

semgrep ci --config=autoがSemgrepレジストリのコミュニティ規則(実測1074件)を実行のたびに取得しており、2026-07に上流でsupply-chain系の新規則が追加されたことで、リポジトリを一切変更していなくても41件がblockingとして検出されmainが赤くなったため。

Q2SEMGREP_RULESで規則セットを宣言していたのに効かなかったのはなぜですか?

旧構成ではenv: SEMGREP_RULESで5つのルールセットを宣言しながら、実行コマンドには--config=autoを渡していたため、その宣言はSemgrepに読まれず常にレジストリ全件(1074件)が走っていた。宣言と実際の挙動が乖離していた。

Q3対処後は完全に決定的になりましたか?

いいえ。p/*の各ルールセット自体もscan時にSemgrepレジストリから取得され中身は上流で更新されうるため、完全な決定論ではない。実際に移行時もp/owasp-top-ten由来のterraform規則3件が新たに顕在化した。

Q4--config=autoは完全に廃止したのですか?

いいえ。blocking判定からは外したが、週次scheduleだけで走るsemgrep-discoveryジョブとして残した。continue-on-error: trueで非blockingにし、新規則の指摘をcode scanningアラートとして可視化する役割に変えた。

Q5pnpmの規則を3件除外しているのはなぜですか?

pnpm-minimum-release-ageとpnpm-block-exotic-sub-dependenciesはpnpm 11の既定で既に有効なため誤検知と判断した。pnpm-trust-policyは--frozen-lockfileでも既存ロック全件に再適用され、上流都合だけでCIが落ちうるため意図的に採用しないことにした。

確認した環境

  • semgrep/semgrepイメージ 1.171.0(digest固定)/ actions/checkout v6.1.0
  • 2026-07-25 にキミテラス-v2のCIで対処(旧構成では2026-07に上流の規則追加でmain恒常赤化)

この記事の根拠

  • YAMLファイル 27〜45行目コミット abb0ba6
  • YAMLファイル 31〜96行目コミット 48f38e3
  • YAMLファイル 98〜120行目コミット 48f38e3
  • YAMLファイル 9〜32行目コミット 48f38e3
  • YAMLファイルコミット 48f38e3

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