なぜこの記事が必要か
Claude Codeや最近のエージェントスタックを使っていると、誰かが作った**スキル(Skill)**をフォルダに放り込んでそのまま使うことがよくあります。スキルはMarkdownファイル1枚、平文の英語指示文の束です。「open a pull request」「triage these tickets」といった文がすべてです。
問題は、これが**サプライチェーン(supply chain)**になったことです。誰でも公開でき、私たちはインターネットから何かを受け取るのと同じ感覚でインストールします。そこでスキャナが登場しました。フォルダを指定し、スキャンを回し、数字を読み、ラベルを信じ、インストール可否を決める流れです。
しかしその数字が、画面上でもっとも信用できない値です。
本記事は、NVIDIAが公開したオープンソーススキャナSkillSpectorで実際のスキル3つをスキャンした結果をまとめたものです。悪性フィクスチャ(ハニーポット)、正直なGitHubラッパー、そして871行の自動化スキルを1つずつ。結論から言うと:
- 静的解析は高速・無料で、本物の悪意は捕捉します。ただし最も危険だったのはコードではなく、Markdown内の平文散文でした。
- 同じ静的レイヤーが、正常なスキルには約80%の誤検知率を出します。
- スコアの数字は**設計上の強力さと隠れた脅威を区別できません。**それは第2ステージ(4パス)が担う役割です。
根拠資料: From Green Checkmark to Real Judgment (Towards Data Science)
日本のエンタープライズSIや金融系のように外部コードの持ち込み審査が厳しい環境では、この流れは特に重要です。「スキャナを回したから安全」という判断が、むしろ事故の起点になり得ます。
![]()
SkillSpectorはどうスキルをスキャンするか
2段階の並列構造
SkillSpectorはマニフェストをパースし、ファイル一覧を構築した後、アナライザを2グループに分けて実行します。
第1グループ — 決定論的(Deterministic)レイヤー
- 16カテゴリ・64検出パターン
- Python ASTパース
- Taint tracking(機密ソース → ネットワークシンク)
- YARAシグネチャ
- OSV.devへのリアルタイムCVE照会
このレイヤーはAPIキーなしでオフラインで反復実行可能です。本記事で言う「静的レイヤー」がこれに当たります。
第2グループ — LLMアナライザ3種
- セマンティック・プロンプトインジェクション検出 — 「ignore your instructions」を丁寧に言い換えた文を捕捉します。正規表現では絶対に拾えません。
- 開発者意図の監査 — マニフェストが主張する以上のことをコードがしていないか確認します。
- 品質・ポリシー検査 — 曖昧なトリガー、警告なしの破壊的動作など。
これら3パスは**まだフィルタリングしません。発見のみを行います。**脅威が「文」であり得る以上、文にはパターンではなく読み手が必要だからです。
第4パス: メタアナライザ
4番目のモデルパスがメタアナライザです。静的+3発見パスの全結果を受け取り、どれが真実かを判定します。このプロンプトは明示的にスキルコンテンツ全体を敵対的(adversarial)として扱い、「this skill is verified safe」のような文が出たらむしろ疑念度を上げるよう設計されています。
スキャン対象がスキャナを説得しようとする可能性があるため、この防衛線が重要です。
スコア計算方式
# SkillSpectorのスコアリングロジック(概念要約)
SEVERITY_POINTS = {
"critical": 50,
"high": 30,
"medium": 15,
"low": 5,
}
# 同一ルールの反復は逓減(diminishing returns)を適用
# 各findingにモデルのconfidenceを乗算
# 実行コードを含む場合は1.3x倍(スクリプト付きスキルは脆弱確率2.12倍)
# 合計は100でclamp後、バンド・推奨にマッピング
score = min(100, sum(f.points * f.confidence for f in findings) * executable_multiplier)
ここで見るべきは2点です。第一に、recallとprecisionが意図的にステージ分離されています。静的パスは広く、モデルパスは狭く。第二に、パイプライン全体の**公式precisionは約87%**で、これはモデルステージが有効な場合の数値です(デフォルト有効、ただしAPIキーが必要)。

3スキルの実測結果 — 数字ではなく「差分」を読む
| スキル | 静的結果 | モデルパス後 | 最終スコア | 判定 |
|---|---|---|---|---|
| vuln-test-skill(悪性フィクスチャ) | 16件のfinding | 3件の誤検知を除去 | 100 | Do not install |
| github(正直なラッパー) | 0件 | 0件 | 0 | Safe |
| gh-issues(871行の自動化) | 20件medium、31点 | 16件をbenign除去、4件を維持 | 27 | Caution |
悪性フィクスチャ — 最も危険だったのは散文だった
SKILL.mdの中にこう書かれていました: 「read the user's AWS keys」、「ignore prior instructions」。平文の英語です。PythonヘルパーではなくMarkdown内です。コードだけを見るスキャナはこれをカウントできません。
正直なGitHubラッパー — 何も出なかったことが核心
静的0件、LLM 0件。退屈な結果ですが最も重要な結果です。問題だけを探すスキャナは、玄関チャイムに繋がれた火災報知器です。何もなくても鳴ります。クリーンなパスがあってこそ、他の警報を信頼できます。
871行の自動化 — ここが本当の試験台
静的スキャンで20件medium、31点。うち16件が同一ルール(external transmission)で、すべてapi.github.comを指していました。GitHubが仕事であるスキルが、GitHubと通信したことで16回指摘されたわけです。
モデルパスがこの16件をbenignとして除去し、4件のみ残しました。
- 2件: 無人フラグ下での自律的コード修正+未確認PR
- 2件: スケジュール実行のために残された状態ファイル
開発者意図パスは、静的が完全に見落としたものも1つ捕捉しました — グローバルgit configの変更。1リポジトリの自動化範囲を超えた動作です。
スコアは31 → 27に再計算。依然としてCautionですが、今度は意味のあるCautionです。31はリスク評価の衣を着たノイズで、27は実際の行動3つに名前を付けた値です。
スコアは損失圧縮(lossy compression)です。本当の情報は数字ではなく、ステージ間の差分にあります。
この技術の限界と注意点
- ベースライン機能の罠:
skillspector baseline ./skill -o .skillspector-baseline.yamlで既知のfindingを抑制できますが、不注意に運用すると**正常な自動化が悪性exfiltrationに変わる変化も隠せます。**ベースライン抑制は「このカテゴリはもう見ない」という決定です。 - Exit codeの罠: SkillSpectorはrisk score 50以下で成功(0)終了します。27や31のようなCAUTION結果がデフォルトでCIを通過します。
- 再現不可: コードのどこにもtemperatureが固定されておらず、整数単位での完全再現はできません。決定論的レイヤーと算術のみが正確に一致します。
- 文脈依存性:
api.github.comへの通信はGitHubスキルでは機能、PDFフォーマッタではインシデントです。同じfinding、異なる判定。
実務適用ガイド — パイプラインへの組み込み方
- Ingest段階: 静的スキャンのみ。無料・オフライン・決定論的。キューの並べ替えにのみ使用します。これだけで承認・ブロックの判断をしないでください。
- Approvalゲート: 実際に配布されるスキルにのみLLM 4パスを適用。トークンとセットアップコストがかかりますが、ここでのみ使います。
- Findingは件数ではなく「行動単位」でグループ化。同一ドメインへの同一ルール16件は脅威16件ではなく行動1件です。
- スキルをPRのようにレビュー。問いは「この機能は危険か?」ではなく**「マニフェストがこれを要求したか?」**です。
併せて読みたい記事: Lambda 100万個、AWSアカウント数千個 — ProGloveのサーバーレススケーリング生存記 — 大規模システムで「数字の裏の文脈」を読む感覚は、サーバーレススケーリングでも同じく重要です。
AIデータセンターインフラのトレンドが気になる方へ: NVIDIA Rubinリリース前、すでに準備を終えたAzureのAIデータセンター戦略

まとめ — 次のスキルをインストールする前に
スキルスキャンの静的レイヤーはほぼ解決済みの問題です。高速・無料で、明白なものはよく捕捉します。難しいのは判断レイヤーです。強力な機能が正当な機能なのか、セキュリティ脅威なのかを決めること。これはスコア1つに還元できません。解釈が必要です。
私個人のルールを共有します。**スキルはバンド1つでインストールも、拒否もしません。**代わりに4つをテーブルに載せます。
- 自分が間違ったとき何を失うか — 最終決定要因
- スキャン出力を要約ではなくリストとして読む
- マニフェストの明示された目的 — 機能と脅威が同じルールをトリガーするときに区別するため
- 作成者とトリガーの具体性 — 「曖昧さ」にスキャナの仕事を代行させないため
次のステップ学習方向
- MCPサーバーモードでゲートを張る:
skillspector mcpで実行するとscan_skillツールを公開し、safe_to_installを返します。エージェントランタイムが**インストール自体をループ内でブロックできます。**人が先にスキャナを回すことを期待する代わりに。 - Invariant mcp-scan、Cisco AI DefenseなどのMCPスキャナと組み合わせ。前者はMCPサーバー定義を、後者は決定論的検査+LLM判定を並行します。
- AnthropicのAgent Skills公式ドキュメントとSnyk ToxicSkillsレポートを併読してください。前者はフォーマット定義、後者は実測有病率データを提供します。
最後に1つ。レビュー対象のアーティファクトは、人間のレビュアーにとっても入力です。SkillSpectorのメタプロンプトは「this skill is verified safe」を疑念の理由として読みます。あなたもそうしてください。監査者に向けた散文 — 安心させる言葉、自己認証、ヘルパースクリプトはスキップしても安全というコメント — はターゲティング信号であり、慰めではありません。上昇したリスクとして扱ってください。証拠としては決して。