ooligo
claude-skill

Audit which agents and MCP servers can write to your CRM

Difficulty
中級
Setup time
45-90 min
For
revops · gtm-engineer
RevOps

Stack

CRM のデータを変更できる非人間アイデンティティ — OAuth の connected app、private app のトークン、MCP サーバー、エージェント連携 — をすべて棚卸しし、CRM が何を認可したかと、エージェントクライアントが実際に到達できるよう設定されているものとを突き合わせる Claude Skill です。バンドルは apps/web/public/artifacts/crm-agent-access-audit-skill/ にあり、SKILL.md と 3 つの参照ファイルを含みます。うち 1 つは初回実行の前に記入します。

この Skill は読み取り専用です。何も失効させず、何もブロックしません。提案する是正措置は組織全体に即座に効くため、実行の判断はエージェントの実行ではなく、名前のある人間に属します。

埋めるギャップ

エージェント連携のアクセスレビューは間違った成果物に対して実施されており、その誤りには 2 つの定番があります。

1 つ目は MCP の tool アノテーションを監査することです。サーバーが tool に readOnlyHint: true を宣言し、レビュー担当者は「読み取り専用の連携」と記録して次へ進みます。そのアノテーションはサーバー自身による自己申告です。MCP 仕様は、アノテーションは「tool の挙動を忠実に記述することが保証されておらず、信頼できるサーバーから来たものでない限り、クライアントはそれを信頼できないものとして扱わなければならない」と述べています。連携が実際に何をできるかは、そのトークンの OAuth スコープが決めており、それは CRM 側にあります。

2 つ目は connected app の一覧だけを監査することです。Salesforce の Connected Apps OAuth Usage ページは、組織が何を認可したかについては正典ですが、どのエージェントがどこで動いてその認可を使っているかについては盲目です。1 つのサービス認証情報を共有する 4 つの MCP サーバーは、そこでは 1 件の許可として現れます。

どちらの視点も間違ってはいません。所見はその差分に存在します。だからこの Skill は両方の面に 3 つ目を加えて収集し、食い違う箇所を報告します。

使うとき

アクセスレビュー、SOC 2 の user access review サイクル、あるいはセキュリティ質問票が CRM 連携を対象にするとき。CRM 連携を持つベンダーがインシデントを公表し、影響範囲の回答が今日必要なとき。エンジニアが自分で MCP サーバーを追加していて、現在の全体像を誰も把握していないとき。あるいは誰かがエージェントの書き込み権限の拡大を提案し、すでに何があるのかと尋ねたとき。

これが想定している形は公開されています。2025 年 8 月 8 日から 18 日にかけて、攻撃者は Salesloft Drift アプリケーションの侵害された OAuth トークンを使い、顧客の Salesforce インスタンスから Account、Contact、Case、Opportunity のレコードを持ち出し、その結果を認証情報の有無で走査しました。影響を受けた組織数は 700 を超えると報じられています。Salesloft は有効な Drift トークンをすべて失効させ、連携は AppExchange から取り下げられました。顧客の Salesforce パスワードが破られたわけではなく、Salesforce の脆弱性が関与したわけでもありません。誰も再点検していなかったスコープを持つ第三者のトークンが、攻撃面のすべてでした。

使わないとき

  • 管理者が 1 人で connected app が 10 件未満の組織。 OAuth Usage ページを直接読んでください。この規模では正規化とスコアリングは過剰です。
  • 片方の面にしか読み取り権限がない。 CRM の設定へのアクセスエージェントクライアント設定を読む手段の両方がなければ、突き合わせフェーズは何も生まず、実行はすでに手元にある一覧に退化します。
  • 是正措置が欲しい。 これが生むのは所見です。references/3-finding-dispositions.md が各是正措置の破壊範囲を示しており、いずれも人間を必要とします。
  • 本当の要件が継続監視である。 これは一時点の監査です。新しい許可へのアラートが必要なら SSPM を買ってください。
  • 所有者レジストリを誰も記入しない。 名前のある所有者がない許可は割り当てられず、所有者不明の許可が 90 件並ぶレポートは意思決定ではなく文書です。

セットアップ

45-90 分を見込んでください。その大半は references/1-grant-inventory-sources.md のパート C — すでに把握している連携を、それぞれ 1 行の実質的な正当化とともに書き出す作業です。このファイルが、割り当てが進む所見と放置される所見の分かれ目になります。

  1. Skill をインストールします。 SKILL.mdreferences/.claude/skills/crm-agent-access-audit/ にコピーします。
  2. 読み取り専用の監査用認証情報を用意します。 フェーズ 0 は監査自身の認証情報が書き込みスコープを持たないことを検証し、結果をレポートのヘッダーに記録します。full で動く監査は読み取り専用を名乗れません。
  3. パート C を記入します。 既知の連携ごとに CSV 1 行 — 識別子、所有者のメール、正当化、承認日、許可ごとのレビュー間隔。収集で見つかりここに無いものは所有者なしとして報告されます。それが意図した動作です。
  4. references/2-blast-radius-rubric.md のパート D で stale_days を設定します。 実際のアクセスレビュー間隔に合わせてください。既定値の 90 日は四半期レビューに対応します。年次でレビューする組織がそのまま引き継ぐとノイズになります。
  5. スコープ表が自組織に合っているか確認します。 ルーブリックのパート A とパート B には主張があります。crm.schemas.*.write が最上位なのは、プロパティ定義の変更が下流の利用者を一度にすべて壊し、行単位で元に戻せないからです。異論は表で述べてください。コードではありません。
  6. まず 1 つの面だけに対して収集を実行し、スコアリングの前に生の出力を読んでください。

この skill が実際に行うこと

6 フェーズ、順序は固定です。フェーズ 5 は面が 3 つ揃わない限り実行を拒否します。

フェーズ 0 は監査自身の姿勢を固定します。 アイデンティティ、その権限、書き込みスコープの有無を run_dir/run-meta.json に記録します。

フェーズ 1 は CRM が認可した許可を収集します。 Salesforce では SOQL クエリ 1 本、SELECT Id, AppName, UserId, CreatedDate, LastUsedDate, UseCount, AppMenuItemId FROM OauthToken を、connected app のスコープを含む設定エクスポートに AppName で結合します。この結合は任意ではなく、多くの実装が飛ばす部分です。OauthToken の行が持つのは利用状況であって能力ではありません。 スコープの列は存在しません。connected app のエクスポートなしにスコープを報告する実行は、捏造したデータを報告しています。HubSpot では private app のトークンを 1 件ずつ POST /oauth/v2/private-apps/get/access-token-info に照会します。インストール済みのパブリックアプリはポータルからの手動エクスポートで、手動と印を付けます。手作業で集めた面は API で集めた面とは古び方が違うからです。

フェーズ 2 はクライアント側の設定を収集します。 claude mcp list に加え、Claude Code の 3 つのスコープはそれぞれ別のファイルです — localuser~/.claude.jsonproject はリポジトリの .mcp.json、さらに Claude Desktop 自身の設定もあります。承認待ちと表示されるサーバーも収集します。ここで効く関門は承認ではありません。エントリ内の認証情報は承認の有無にかかわらずすでに存在するからです。収集器は環境変数の名前だけを記録し、値はレポート生成時ではなくパース時に [redacted] として書き出します。

フェーズ 3 は宣言された能力を収集します。 CRM に触れる各サーバーの tool 一覧と 4 つのアノテーションヒントを、主張として記録します。ここでは存在よりも不在のほうが重く働きます。仕様の既定値がそうさせます。readOnlyHint の既定は falsedestructiveHinttrueidempotentHintfalseopenWorldHinttrue です。アノテーションのない tool は書き込み可能かつ破壊的と規定されているため、アノテーションのない CRM サーバーは未知ではなく、書き込み側と推定されます。

フェーズ 4 は正規化とスコアリングを行います。 ルーブリックの表に対してコードとして実行します。アクセスレビューは四半期ごとに再実行され、その価値は差分にあります。同じ 90 件の許可を 2 回並べ替えさせたモデルは 2 通りの順序を返し、差分は読めなくなります。モデルの判断は各所見の正当化の段落を書くだけで、レベルを決めることはありません。

フェーズ 5 は突き合わせます。 差分は 5 種類出ます。orphan-grant(生きた認可、設定なし、所有者なし — Drift の形)、unattributed-write(1 つの認証情報の背後に複数のサーバー。監査行は書き込みが起きたことは証明できても、どのエージェントが行ったかは特定できない)、annotation-mismatch(読み取り専用と宣言しつつ書き込みを付与されている)、stale-grant(しきい値を超えて未使用で refresh token が生きている)、scope-excess です。

フェーズ 6 は報告します。 レベル順、次に経過時間順で並べ、各所見に証跡のパスを添えます。レビュー担当者が要約と言い争う代わりに生の行を読めるようにするためです。

コストとスループット

API のコストはほぼゼロで、人的コストが予算のすべてです。

Salesforce の面 A は組織規模にかかわらず SOQL クエリ 1 本と、大きな結果セットでのページングです。HubSpot は private app のトークン 1 件につき照会 1 回 — 25 トークンなら 25 回です。面 B はファイルシステムの読み取りとローカル CLI の実行で、API コストはゼロです。中規模の組織なら API 呼び出しは合計 100 回未満に収まり、収集は数分で終わります。

スコアリングが決定的なので、トークンコストは低いままです。モデルに届くのは正当化の段落だけで、所見 1 件あたり出力 300-400 トークン程度。所見 90 件の初回実行でも 1 ドルをかなり下回ります。45-90 分のセットアップは 1 回きりで、パート C が残るため以降の実行すべてに償却されます。

本当に見積もるべき数字は所有者の追跡です。レジストリを一度も持たなかった組織に対する初回実行では、所有者不明の件数がレポートを支配し、その所有者を突き止めるには計算時間の数分ではなくカレンダー上の数日がかかると見てください。その作業は付随費用ではなく、監査そのものです。

成功指標

帰属までの時間を追ってください。許可の識別子を与えられてから、名前のある人間が所有と正当化を確認するまでの時間です。最初は日単位ですが、パート C が埋まれば分単位で終わるはずです。これは実際のインシデントで組織がどう動けるかを予測する数字です。そのとき問われるのは、90 件の連携のどれが侵害されたトークンを持っていたかだからです。

副次的な指標は所有者不明の許可数の四半期推移です。四半期ごとに再び増える数字は、許可が登録より速く作られていることを意味します。手当ては許可の発行時点に登録手順を置くことであり、より大きな監査ではありません。

失敗モード

  • OauthToken にスコープ列はなく、それでもスコープを報告する実行はそれを捏造している。 ガード: フェーズ 4 は scope_sourceconnected-app-export でない Salesforce の許可をハード失敗させます。欠落したスコープは unknown として描画され、解決するまで最上位でスコアリングされるので、欠落が目立ちます。
  • きれいなレポートと壊れたクエリは見分けがつかない。 所見ゼロは安全と読まれます。ガード: フェーズ 5 は面が 3 つ揃わなければ実行を拒否し、レポートのヘッダーは面ごとのレコード数を印字します。0 行の面は所見ゼロではなく COLLECTION FAILED と印字されます。
  • 監査は生きた秘密で埋まった設定ファイルを読み、ディスクに書き出す。 設定ブロックをそのまま引用したレポートは、後で監査人にメールされる文書に認証情報を漏らします。ガード: マスキングはパース時に行い、何かが run_dir に届く前に済ませます。レポート生成時にマスキングしたファイルは、すでに生ディレクトリへ漏れています。
  • connected app のブロックは即時かつ組織全体に及ぶ。 ユーザー単位の Block も段階的な展開もなく、全ユーザーの認可が同じ呼び出しで死にます。ガード: 処置は提案であり、承認者名と通知期間を伴います。処置 R2 は事前に UseCountLastUsedDate を確認することを求めます。登録所有者がないのに利用回数が 14,000 なら、それは連携が放棄されているのではなく、所有者の調査が不十分だったということです。
  • オフボーディングは生きた許可を残す。 Salesforce のユーザーを無効化しても、そのユーザーの OAuth 認可は失効しません。連携を一度でも認可した退職者は、開いたままの許可を残します。ガード: 処置 R3 は退職ごとにアプリの全一覧に対して実行します。誰かが覚えているアプリだけではありません。
  • Use Any API Client は API Access Control を迂回する。 サポートケースを起票し、許可リストを有効化し、これで解決したと考えている組織にも、その権限が割り当てられている場所には迂回路が残ります。ガード: パート D はこの権限の割り当て先自体を許可レコードとして収集し、許可リストの内容にかかわらず書き込み可能として格付けします。

vs 代替案

vs SSPM 製品(AppOmni、Obsidian、Valence)。 これらは継続的に監視し、2 つの CRM よりはるかに広い SaaS を対象にし、ベンダーリスクの独自インテリジェンスを維持します。このバンドルが持たず、主張もしない利点です。大規模な SaaS 群にわたって新しい許可へアラートを出すことが要件で、予算があるなら買ってください。この Skill が勝つのは、それらの製品が最も手薄な面です。すなわちエージェントのクライアント側で、MCP サーバーが IdP や SaaS 管理コンソールではなく開発用ノート PC の設定ファイルに存在する領域です。

vs Salesforce のネイティブ機能のみ。 OAuth Usage ページと API Access Control は無償で、面 A については正典です。API Access Control はここで使える単体としては最強の統制で、API の到達範囲を許可リストに固定します。限界は 2 つ。有効化にサポートケースが要るため即日の回答にはならず、承認済みアプリをどのエージェントが使っているかも、MCP サーバーが自身について何を宣言しているかも語りません。両方使ってください。この Skill のパート D は、まさにその許可リストの迂回路を収集するために存在します。

vs 表計算による年次アクセスレビュー。 これが正直な比較です。ほとんどのチームが実際にやっているのはこれだからです。表計算は無償で、セットアップも要りません。同時に、証跡のない一時点のスナップショットであり、レビュー担当者が尋ねようと思いついたものしか捉えられず、誰も列挙しなかった許可に気づく仕組みを持ちません。バンドルの grants.jsonl は、次回のレビューを再調査ではなく差分にするために存在します。

vs 直接スクリプトを書く。 面 A は実際に書きやすく、クエリ 1 本とエクスポート 1 回です。書き直さないほうがよいのは、スコープからレベルへの表、修正子の計算、そして破壊範囲の注記を伴う処置カタログです。判断が宿っているのはそこだからです。面 A だけが必要なら直接書いてください。突き合わせが要るならバンドルを使ってください。

関連: この監査が見つけるために設計された、統制された書き込み経路の hubspot-agent-cli-crm-cleanup-skill、および面 B に現れる種類のサーバーの例として mcp-server-gong-revops

Files in this artifact

Download all (.zip)