mcpField
S
scanner-pipeline

scanner-pipeline

github.com/briiirussellセキュリティ51455 回インストール1 週間前に更新
リポジトリ

scanner-pipeline について

scanner-pipelineは、リポジトリの各レイヤーに適した決定論的なセキュリティスキャナーを選び、実行して、結果を正規化し、修正担当への引き継ぎを準備するスキルです。対象リポジトリ内にスキャン計画、ワークフロー、スキャナー設定も作成します。稼働中のアプリをスキャンするには許可が必要です。

主な機能

  1. 01リポジトリのレイヤーごとにスキャナーを選択
  2. 02非対話コマンドでスキャナーを実行
  3. 03結果を正規化して重複を除去
  4. 04正規化した結果をSARIFで出力
  5. 05スキャン計画とCIワークフローファイルを作成

活用シーン

  1. 01複数エコシステムを含むリポジトリのセキュリティ検査
  2. 02依存関係、コード、シークレット、インフラの結果を集約
  3. 03スキャン結果のトリアージと修正を準備

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/briiirussell/cybersecurity-skills --skill scanner-pipeline

インストール後、Claude Code で/scanner-pipelineを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

references/
betterleaks-to-findings.mjs
findings-format.md
findings.schema.json
security-scan.yml
to-sarif.mjs
validate-findings.mjs
references/semgrep/
supabase-rls.yml

詳細

Scanner Pipeline — 実際のツールを実行し、正規化して引き渡す

エージェントにアプリを即興で改造させるのはやめましょう。各レイヤーに適した決定論的なスキャナーを選び、非対話型で実行して、出力を単一の形式に統合し、各検出結果を修正エージェントに振り分けます。スキャナーは毎回同じ問題を検出します。エージェントによるレビューで検出されるのは、たまたま目にしたときだけです。

2026年10月の45アプリの一斉調査(patch-sweep)で得られた実例です。エージェント主導のレビューでも最終的にはすべて検出できましたが、スキャナーなら初回の実行で各問題を検出していたはずです。

  • RLSが無効になっているSupabaseテーブルが10個あり、公開用のanonキーで読み書きできる状態でした。その中には、472k件の受信メール本文も含まれていました。Supabase独自のセキュリティアドバイザーは、これをrls_disabled_in_publicとして報告します。
  • 常にtrueになるRLSポリシー:csr.collection_id = csr.collection_id。マイグレーションに対するカスタムsemgrepルールで検出できます(以下を参照)。
  • リポジトリごとに個別にnpm audit、pnpm audit、bundle-auditを実行していましたが、osv-scannerを1回実行すれば、すべてのロックファイルをスキャンできます。

このスキルはオーケストレーションを行います。 ツールの検出結果を詳しく解釈する作業は、各レイヤーのスキル(secrets-audit、dependency-audit、container-audit、cloud-audit、owasp-audit)が担います。実際の侵入テストにはweb-pentest/red-team-engagementを使います。

Writeが事前承認されている理由: このスキルの主な出力はファイルです。scanner-pipeline.mdの計画書、.github/workflows/security-scan.yml、およびスキャナー設定を書き込みます。書き込むのはこれらに限られ、対象リポジトリ内のみです。稼働中のアプリを対象とするスキャナー(nuclei、zap)は意図的に事前承認されていません。これらは必ず確認を求めます。

関連項目:finding-triage(優先順位付けと対応方針)、secrets-audit、dependency-audit、container-audit、cloud-audit(Supabaseのアドバイザー、PaaS)、web-pentest(稼働中のシステムをスキャンするための承認)、cicd-security(SHAで固定したActions、ワークフローの強化、zizmor/actionlintの結果)、patch-sweep(複数リポジトリにわたるCVE対応)、log-watch。

承認

何かを実行する前に、以下を確認してください。

  1. リポジトリ: ユーザーが各リポジトリを所有しているか、スキャンする権限を持っていること。一斉スキャンの場合は、列挙する所有者/組織を確認します。
  2. 稼働中のターゲット: ユーザーが所有し、この会話で明示的に名前を挙げたドメインのみを対象とします。「リポジトリがデプロイされている先はどこでも」ではなく、ユーザー自身にホスト名を指定してもらいます。
  3. 結果の保存先: ローカルファイルのみ、GitHubコードスキャン、またはイシューのいずれかを確認します。シークレットの検出結果を公開イシューに記載してはいけません。

以下の操作は拒否してください。ユーザーが所有していない、または名前を挙げていないホストに対する稼働中スキャナー(nuclei、ZAP、Burp)の実行、サードパーティまたは共有ホスト(共有Supabase/Vercel/Herokuホスト名、CDN、呼び出し先のAPI)のスキャン、web-pentestによる承認を得ずに行うZAPのアクティブスキャンまたはnucleiの侵入的なテンプレートの実行、およびシークレット値をどこかに表示すること。

1. スタックを検出し、レイヤーごとにツールを選ぶ

まずリポジトリを確認します。ロックファイル、Dockerfile、*.tf、supabase/、Gemfile、go.mod、.github/workflows/を調べます。その後、パッケージマネージャーごとのツールより、複数のエコシステムに対応するツールを優先して、レイヤーごとに1つのツールを選びます。

レイヤー主なツールスタック固有の追加ツールフォールバック
シークレットbetterleaks git または gitleaks git(全履歴)生存確認。確認を取ってから、betterleaks … -v/-a、または trufflehog git --only-verified—
コード(SAST)semgrep scanBrakeman(Rails)、gosec(Go)、CodeQL(GHAS を使用している場合)、カスタム Semgrep ルール(以下)—
依存関係osv-scanner scan source -r .(npm、pnpm、yarn、Bundler、pip、Go、Cargo、Composer、Maven を一括スキャン)bundle-audit(Rails)、govulncheck(Go、コールグラフによる到達可能性分析)、pip-auditnpm audit/pnpm audit --prod/yarn audit
インフラ/IaC/コンテナーtrivy fs --scanners vuln,misconfig,secret .checkov -d .(Terraform、CloudFormation、k8s、Actions)、trivy image <img>—
データベース(Supabase)Supabase セキュリティアドバイザー(supabase db advisors、または Management API GET /v1/projects/{ref}/advisors/security)supabase db lint(plpgsql_check:関数のエラーを検出しますが、RLS は検出しません)、supabase/migrations/ を対象にした Semgrep ルールpg_policies/pg_tables.rowsecurity クエリ
稼働中のアプリ(保護された環境)ZAP ベースライン(パッシブ)-rl を指定した nuclei—

コマンドは非対話形式で実行し、各ツールの出力ファイルを.security/out/に1つずつ保存します。

mkdir -p .security/out
# Secrets — full history, values redacted
gitleaks git . --redact --report-format sarif --report-path .security/out/gitleaks.sarif
# or Betterleaks: detection only and silent, so a stray BETTERLEAKS_VALIDATE=true can't use found keys and nothing prints.
# v2 has no SARIF reporter: convert, then export (needs_validation records go out at level "note")
betterleaks git . --validate=false --analyze=false --redact --silent -o .security/out/betterleaks.json
node references/betterleaks-to-findings.mjs .security/out/betterleaks.json -o .security/out/betterleaks.findings.json
node references/to-sarif.mjs .security/out/betterleaks.findings.json --include-needs-validation -o .security/out/betterleaks.sarif
trufflehog git file://. --only-verified --json --no-update | jq -c 'del(.Raw, .RawV2)' > .security/out/trufflehog.jsonl
# Code
semgrep scan --config p/default --config .semgrep/ --metrics=off --sarif --output .security/out/semgrep.sarif
# Dependencies (all lockfiles at once)
osv-scanner scan source -r . --format sarif --output .security/out/osv.sarif
# Infra / IaC / container files
trivy fs --scanners vuln,misconfig,secret --format sarif --output .security/out/trivy.sarif .
checkov -d . -o sarif --output-file-path .security/out/
# Rails
bundle-audit check --update
brakeman --no-pager -f sarif -o .security/out/brakeman.sarif
# Go
govulncheck -format sarif ./... > .security/out/govulncheck.sarif
gosec -fmt sarif -out .security/out/gosec.sarif ./...
# Supabase (check flags with `supabase db advisors --help` for your CLI version)
supabase db advisors --linked
curl -s -H "Authorization: Bearer $SUPABASE_ACCESS_TOKEN" \
  "https://api.supabase.com/v1/projects/<ref>/advisors/security" > .security/out/supabase-advisors.json

.security/out/を.gitignoreに追加します。バージョンによってツールのメジャーバージョン間で動作が異なる場合があります(gitleaks 8.19+ はgit/dirサブコマンドを使用します。osv-scanner v2 はscan sourceを使用します。Betterleaks v2 ではdirがfsに、--report-pathが-oに、--validationが-vに変更されました。v1 では--verboseを意味していました)。推測せず、<tool> --helpを一度実行して調整してください。

カスタムルール:常に true になる RLS ポリシー

このルールを作るきっかけとなったバグは、using (csr.collection_id = csr.collection_id)が列をそれ自身と比較しているため、すべての行でポリシーが通過してしまうことです。作成者が意図していたのは、外側のテーブルの列でした。Semgrep には SQL 用のファーストクラスパーサーがないため、汎用モードを使います。pattern-inside: create policy ... ;と、左辺をバックリファレンスする正規表現を組み合わせます。自己比較、リテラルのtrue、および RLS のクロスチェック対象となる公開テーブルを含むルールファイル全体は、references/semgrep/supabase-rls.ymlを参照してください。CI ワークフローで検出されるよう、このファイルをリポジトリの.semgrep/にコピーします。確認済みの検出結果から、テストを含むこのようなルールを作成する方法については、variant-analysisを参照してください。

semgrep scan --metrics=off --config .semgrep/supabase-rls.yml supabase/migrations/
# quick check without semgrep (PCRE backreference)
rg -nP '(?i)\b([a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*)\s*=\s*\1\b' supabase/migrations/
# portable: BSD and GNU grep -E accept \1 in practice (ugrep aliased as grep does not; use /usr/bin/grep)
grep -rEni '([a-z_][a-z0-9_]*\.[a-z_][a-z0-9_]*)[[:space:]]*=[[:space:]]*\1([^a-z0-9_]|$)' supabase/migrations/

データベースが必要でソースコードだけでは確認できない、RLS 無効化チェックも併用してください。

select schemaname, tablename from pg_tables
where schemaname = 'public' and rowsecurity = false;

2. シークレットは慎重に扱います

  • Betterleaks または gitleaks。 ルール ID と gitleaks:allow コメントは同じです。Betterleaks は、汎用シークレットによる誤検知が少なく、検出結果ごとの値のハッシュと有効性チェックを追加します。v2 はまだリリース候補版です(betterleaks version。パッケージマネージャーでは v1.9.0 がインストールされる場合があります)。SARIF は廃止されており、--config を指定しない限り .betterleaks.toml は読み込みません。フラグ、安全ルール、レポート項目については、secrets-audit → references/betterleaks.md を参照してください。
  • HEAD ではなく、履歴全体をスキャンします。 gitleaks git はすべてのコミットを調べますが、gitleaks dir が確認するのは作業ツリーだけです。昨年削除されたシークレットも、すべてのクローンに残っています。
  • 検証済みとは、有効であるということです。 trufflehog --only-verified は、プロバイダーの API を使って各候補をチェックします。その際、見つかったキーがプロバイダーに送信され、使用されます。そのため、trufflehog は事前承認されていません。実行時に確認が求められ、ユーザーがチェックを許可する必要があります。検証済みの検出結果は P0 です。confirmed として記録し、履歴をクリーンアップする前にローテーションしてください。先に履歴を書き換えると、作業中もすべてのフォークとクローンに有効なキーが残ります。ローテーションは secrets-audit に引き継いでください。
  • 値は絶対に出力しません。 gitleaks では --redact を使い、trufflehog の JSON から Raw/RawV2 を削除してください。レポート、イシュー、PR コメント、エージェントへの引き継ぎには、検出ツール、ファイル、コミット、値のハッシュを記載し、値そのものは記載しません。シークレットには secret:<detector>:<sha256(value)[:12]> の形式でフィンガープリントを付けると、5 つのファイルにある同じキーを 1 件の検出結果にまとめられます。Betterleaks は、値を秘匿する前に計算したハッシュを match.fingerprint に格納します。references/betterleaks-to-findings.mjs は、この値からレコードを作成します。gitleaks の --redact と、Raw を削除した trufflehog では値が保持されないため、検出結果はファイルとコミットによってまとめられます。マシンの外部に出るハッシュ(CI アーティファクト、イシューなど)には --hmac-key を使用してください。短いトークンの SHA-256 ハッシュは総当たりで解読される可能性があります。
  • 未検証の検出結果はノイズではなく、needs_validation です。プロバイダーが検証に対応していないか、キーが内部システム用である可能性があります。

3. 稼働中のアプリをスキャンする際のガードレール

稼働中のシステムをスキャンすると、そこにトラフィックが送信されます。まず web-pentest の認可手順に従ってください。このセクションでは、頻度と順序のみを定めます。

  1. ユーザーが所有し、指定したホスト名のみを対象にします。 プラットフォームの共有ドメインのルート、CDN、認証プロバイダー、アプリが呼び出すサードパーティ API は、絶対に対象にしないでください。ワイルドカードや DNS から取得したリストは使わず、-u https://app.example.com を使ってください。
  2. アクティブスキャンの前にパッシブスキャンを行います。 ZAP のベースラインスキャンは、パッシブにクロールしてレポートを作成します。
    docker run --rm -t ghcr.io/zaproxy/zaproxy:stable zap-baseline.py -t https://app.example.com -J zap.json
    
    アクティブスキャン(zap-full-scan.py)は、ユーザーの確認後にのみ実行してください。できればステージング環境のコピーで実施してください。
  3. 低頻度に抑えます。 nuclei -u https://app.example.com -rl 10 -c 5 -severity medium,high,critical -exclude-tags intrusive,dos,fuzz -jsonl -o nuclei.jsonl を実行します。頻度を上げるのは、ユーザーの許可を得てからにしてください。
  4. 被害の兆候が見られたら停止します。 5xx の急増、レート制限の応答、アカウントのロックアウトが発生した場合は、報告して一時停止してください。

4. 出力を正規化する

各ツールは異なる形式を出力します。すべてをリポジトリの検出結果形式に変換してから、SARIF を一度だけエクスポートします。形式、フィールド、バリデーターについては references/findings-format.md を参照してください。references/validate-findings.mjs で検証し、references/to-sarif.mjs でエクスポートします。

インポートルール:

  • スキャナーの検出結果は verdict: "needs_validation" として取り込み、blockers: ["scanner result, not yet triaged"] を設定します。また、正確な再実行コマンドを指定する validation_plan を設定します。ツール独自の重大度は evidence[].description に記録し、finding-triage で確認された場合にのみ、レコードに実際の severity を設定します。例外として、検証済みの有効なシークレットはインポート時に confirmed とします。
  • skill: "scanner-pipeline" を設定し、evidence にツール名とルール ID を含めます。
  • cwe にはルールのメタデータを使用します(semgrep、Brakeman、gosec、checkov にはすべて CWE が含まれています)。依存関係のアドバイザリでは、アドバイザリに CWE があればそれを使用し、なければ空欄にします。推測で補わないでください。
  • confidence:静的解析のみの結果は medium を上限とします。検証済みのシークレットと、到達可能性が確認された govulncheck の結果は high にできます。

ツール間で重複を排除するには、フィンガープリントを使用します。フィンガープリントは、行番号、重大度、判定を含まない、根本原因を安定して識別する ID です。

種類フィンガープリント統合対象
依存関係dep:<ecosystem>:<package>:<canonical-id>(CVE があれば CVE、なければ GHSA、それもなければ OSV ID)同じアドバイザリを検出した osv-scanner + trivy + npm audit + Dependabot
シークレットsecret:<detector>:<sha256(value)[:12]>ファイルやコミットをまたいで見つかった同じキー。Betterleaks と gitleaks はルール ID を共有します
コードsast:<cwe>:<file>:<enclosing function or route>同じシンクを検出した semgrep + CodeQL + Brakeman
IaCiac:<resource address>:<check family>同じリソースを検出した trivy misconfig + checkov
データベースdb:<project>:<schema.table>:<lint name>同じテーブルを対象とする advisor + カスタム semgrep ルール

統合後のレコードには、各ツールのエビデンスをすべて残してください。修正エージェントに必要です。

抑制ファイル — .security/suppressions.yml。フィンガープリントごとに 1 件記載します。

- fingerprint: dep:npm:next:CVE-2026-XXXXX
  tool: osv-scanner
  reason: "Static export; no server runtime. Advisory requires Server Actions."
  owner: "@jdoe"
  added: 2026-10-26
  expires: 2027-01-24      # max 90 days; expired entries resurface the finding

ルール:すべてのエントリに理由、担当者、有効期限を設定します(最長 90 日)。期限切れのエントリは無視され、検出結果が再び表示されます。エージェントが抑制を提案し、ユーザーが各項目を承認します。ツールが理由と有効期限に対応している場合に限り、ネイティブの無視ファイルにもエントリを反映します。たとえば、osv-scanner.toml の [[IgnoredVulns]] に ignoreUntil と reason を設定し、.trivyignore に exp:YYYY-MM-DD を設定します。suppressions.yml を信頼できる唯一の情報源として維持してください。抑制された検出結果は findings.json に rejected として記録し、ruled_out_by から抑制エントリを参照します。これにより、再実行時に再度検出されることを防げます。

5. フリートモード

gh repo list <owner> --limit 1000 --source --no-archived --json nameWithOwner,defaultBranchRef,pushedAt

--source はフォークを除外し、--no-archived はアーカイブ済みリポジトリを除外します。オーナー/組織ごとに繰り返し実行します。シークレットのみを対象とする場合、クローンのループを使わず、Betterleaks で組織内の全リポジトリを一度のプロセスで履歴全体にわたってスキャンできます:betterleaks github --validate=false --analyze=false --redact --silent -o .security/out/fleet-betterleaks.json https://github.com/<org>(環境に GitHub トークンが必要です。--exclude-repo '<org>/*-archive' を指定できます。フォークは --include=forks を指定した場合のみ対象になります)。対話形式でプロンプトが表示されます。各リポジトリについて、シークレットを除き、本番ブランチ(patch-sweep のステップ 2 で取得済みならそのブランチ、そうでなければデフォルトブランチ)をシャロークローンします。シークレットには全履歴が必要です(gitleaks では git clone --filter=blob:none で十分です)。リポジトリごとのパイプラインを並列で(数件ずつ)実行し、.security/out/<repo>/ に書き込みます。その後、結果を集約します。

| repo | layer | tool | sev | finding | status |
|---|---|---|---|---|---|
| acme/api | db | supabase advisor | (tool: ERROR) | rls_disabled_in_public: public.inbound_emails | needs_validation |
| acme/api | secrets | trufflehog | P0 | verified Stripe live key in commit a1b2c3d (value hash 3f9e…) | confirmed — rotate |
| acme/web | deps | osv-scanner | (tool: CRITICAL) | next 15.5.10 — CVE-2026-XXXXX | needs_validation |

finding-triage が実際の重大度を設定するまでは、sev にツール独自のラベルが括弧付きで表示されます。まず P0 のシークレットを並べ、その後はツールの重大度順に並べます。

6. CI とスケジュール

references/security-scan.yml を .github/workflows/security-scan.yml としてリポジトリに配置します。これは次の処理を行います。

  • すべての PR で gitleaks、osv-scanner、semgrep を実行します(gitleaks は PR のコミットを対象に実行し、semgrep はベースブランチとの差分を考慮して実行します)。
  • 毎週のフルスキャン(Git の全履歴、すべてのルール)と workflow_dispatch で実行します。
  • workflow_call を使って他のリポジトリから呼び出せるため、1 つのコピーで複数のリポジトリに対応できます。
  • 利用可能な場合は SARIF を GitHub code scanning にアップロードし(公開リポジトリ、または GitHub Advanced Security を利用するプライベートリポジトリ)、それ以外の場合は SARIF を常にワークフローアーティファクトとして保持します。
  • すべての Action を完全なコミット SHA に、すべてのコンテナイメージをダイジェストに固定します(cicd-security)。テンプレート内の SHA は 2026-10-26 に上流のリリースタグを調べて取得したものです。タグへの切り替えではなく、Dependabot または Renovate で更新してください。

CI での Betterleaks(任意、v2 が安定した後)。gitleaks の隣にジョブを追加します。

  • 他と同様に、イメージをダイジェストで固定します。:latest や変動する :v2 タグは使用しないでください。
  • ステップの env で BETTERLEAKS_VALIDATE: "false" と BETTERLEAKS_ANALYZE: "false" を設定し、--validate=false --analyze=false を渡します。CI では絶対に検証しないでください。 フォークからのプルリクエストでは、検証によって PR 作成者が仕込んだキーがルールで指定されたエンドポイントに送信される可能性があります。
  • .betterleaks.toml はベースブランチから読み込み(git show "origin/$GITHUB_BASE_REF:.betterleaks.toml")、--config で渡します。PR のブランチでファイルを編集すると、検出対象のルールを無効化できてしまいます(cicd-security)。
  • レポートアーティファクトがマシンの外に出るため、シークレット(BETTERLEAKS_FINGERPRINT_HMAC_KEY)から --hmac-key を指定します。
  • betterleaks-to-findings.mjs で変換し、次に to-sarif.mjs --include-needs-validation を実行してからアップロードします。v2 は検出結果があると終了コード 1 を返します。gitleaks のステップと同様に continue-on-error: true を維持してください。

code scanning を利用できない場合は、コメントに SARIF をそのまま出力するのではなく、フィンガープリントごとに 1 件の Issue を作成または更新します(タイトル = フィンガープリント、本文 = シークレット値を含まない証拠)。

7. 修正への引き継ぎ

  1. まずトリアージします。 すべての needs_validation レコードを finding-triage に渡し、判定、重大度、処置を決めます。検証済みの P0 シークレットはキューを経由せず、ローテーションに回します。
  2. 修正エージェント 1 つにつき検出結果 1 件とします。 統合済みレコード(フィンガープリント、各ツールの証拠、再実行する正確なコマンド、参照すべき担当レイヤーのスキル)を渡します。ボード全体は渡しません。
  3. 修正ブランチで対象のツールを再実行して確認します。同じコマンド、同じ設定を使い、実行前と実行後を記録します。そのツールが該当するフィンガープリントを報告しなくなった場合にのみ、検出結果をクローズします。
  4. 検出結果を消すためにスキャナーの設定を編集してはいけません。 ルールの削除、対象パスの限定、修正エージェントによる抑制の追加は禁止です。検出結果が誤検知だと判断した修正エージェントは、その旨を報告し、ユーザーが承認する抑制を依頼します。

出力形式

リポジトリのルートに scanner-pipeline.md を作成します(またはリポジトリ群ごとに 1 つ作成します)。

# Scanner Pipeline — [repo or fleet] — [date]

## Stack detected
[languages, package managers, frameworks, IaC, Supabase y/n, live targets named by user]

## Tools per layer
| layer | tool | version | command | output |
|---|---|---|---|---|

## Schedule
- PR: gitleaks (PR commits), osv-scanner, semgrep (diff-aware)
- Weekly: full history + all layers, [day/time UTC]
- Results: code scanning / artifact + issues

## Suppression policy
`.security/suppressions.yml`; reason + owner + expiry ≤ 90 days; user approves each entry.

## First run
- findings.json: [N] records ([c] confirmed, [n] needs_validation, [r] rejected)
- Roll-up board: [table above]

## Hand-off
[fingerprints sent to finding-triage; P0 secrets sent to rotation]

任意のスキャフォールド:.github/workflows/security-scan.yml、.gitleaks.toml(組織固有のルールでデフォルト設定を拡張)、.semgrep/(RLS ルールなどのカスタムルール)、osv-scanner.toml。

境界

  • オーケストレーションのみを担当します。詳細な解釈はレイヤースキルで行います。
  • アクティブなエクスプロイトは行いません。検証が必要なスキャナーの検出結果は、認可手順を経て web-pentest に渡します。
  • ライブスキャンでは、所有者が明確なホストのみを対象とし、ホスト名を指定して、まず受動的に、低いレートで実施します。
  • シークレット値を出力、コミット、投稿してはいけません。
  • CI を通すためにスキャナーの設定を弱めてはいけません。

参考資料

コメント