1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

『AIセキュリティリスク診断 2026』OWASP LLM Top 10を診断フォームに実装してみた—プロンプトインジェクション対策を22問に落とした話

1
Posted at

ai-security-risk-check.jpg

職場でChatGPTを使い始めた人から「これ、情報漏洩しないの?」と聞かれた。

「たぶん大丈夫」と答えたが、正直よくわかっていなかった。調べてみたら、LLM(大規模言語モデル)専用のセキュリティフレームワーク「OWASP LLM Top 10」というものがあった。

で、これを診断フォームにしたら面白いかもと思って作った。完成したもの: AIセキュリティリスク診断

この記事はその実装記録だ。「LLMセキュリティって何があるの?」という人の役にも立てばいいなと思っている。


この記事でできること

  • LLM(大規模言語モデル)特有のセキュリティリスク6種類を理解できる
  • OWASP LLM Top 10 2025 の主要リスクをコードに落とし込む考え方がわかる
  • 22問の診断フォームをVanilla JSで実装する手順がわかる
  • 実際に動くツールはAIセキュリティリスク診断で確認できる

環境・前提

  • ブラウザ: モダンブラウザ(Chrome/Firefox/Safari最新版)
  • JavaScript: Vanilla JS(ES2020)。フレームワーク不使用
  • D3.js: v7(レーダーチャート描画用。動的ロードで読み込む)
  • CMS: WordPress(SWELLテーマ。外部scriptタグが動かない制約あり)
  • 追加依存: なし

完成形

22問に答えると、6軸(データ/モデル/運用/ガバナンス/サプライチェーン/コンプライアンス)のレーダーチャートと業界ベンチマーク比較が出る。NIST AI RMF、OWASP LLM Top 10、総務省ガイドラインを参照した設問になっている。

動くものを先に見たい人: AIセキュリティリスク診断


手順

Step 1: LLMセキュリティの6軸を設計する

まず「何を診断するか」を決める必要がある。

OWASP LLM Top 10 2025を見ると、LLM固有のリスクが10個ある。

  • LLM01: プロンプトインジェクション(最重要)
  • LLM02: 安全でない出力の取り扱い
  • LLM03: 訓練データの汚染
  • LLM05: サプライチェーンの脆弱性
  • LLM06: 機密情報の漏洩
  • LLM08: 過度な権限付与(AIエージェントに広すぎる権限を渡す)
  • LLM10: モデルの窃盗

10個をそのまま10軸にすると設問が多すぎる。似た概念をまとめて6軸にした。

const AXES = [
  { id: 'data',       label: 'データセキュリティ',   questions: ['q1','q2','q3','q4'] },
  { id: 'model',      label: 'モデルセキュリティ',   questions: ['q5','q6','q7'] },
  { id: 'operation',  label: '運用セキュリティ',     questions: ['q8','q9','q10','q11'] },
  { id: 'governance', label: 'ガバナンス',           questions: ['q12','q13','q14','q15'] },
  { id: 'supply',     label: 'サプライチェーン',     questions: ['q16','q17','q18','q19'] },
  { id: 'compliance', label: 'コンプライアンス',     questions: ['q20','q21','q22'] }
];

つまずきポイント: 最初はOWASP LLM Top 10の番号順に軸を作ろうとした。でも「ユーザーが答えやすい順序」と「フレームワークの順序」は別物だった。一般のビジネスマンにとって「データ管理→AIシステム→日常運用→ルール整備」の順番のほうが自然に答えられる。


Step 2: 設問データをJSONで定義する

設問はJavaScriptのオブジェクトとして管理する。後からメンテナンスしやすくするためだ。

const QUESTIONS = {
  q1: {
    axis: 'data',
    text: 'AI学習データ・入力データに含まれる個人情報・機密情報を分類・管理していますか?',
    ref: 'AI事業者ガイドライン 1.2版'
  },
  q8: {
    axis: 'operation',
    text: 'プロンプトインジェクション対策(入出力フィルタリング等)を実装していますか?',
    ref: 'OWASP LLM01'
  },
  // ... 22問分
};

ポイント: ref(参照フレームワーク)を各設問に持たせておくと、結果画面で「この質問はどの規制に基づいているか」を表示できる。


Step 3: 4段階スコアリングを実装する

「やっている」「やっていない」の2択では粒度が荒い。4段階にした。

const CHOICES = [
  { value: 3, label: '十分に実施している' },
  { value: 2, label: '一部実施している'   },
  { value: 1, label: '検討中・計画段階'   },
  { value: 0, label: '未対応'             }
];

function calcAxisScore(axisId, answers) {
  const axis = AXES.find(a => a.id === axisId);
  const maxScore = axis.questions.length * 3;
  const rawScore = axis.questions.reduce(function(sum, qid) {
    const val = answers[qid];
    return sum + (val !== undefined ? val : 0);
  }, 0);
  return Math.round((rawScore / maxScore) * 100);
}

つまずきポイント: && 演算子をWordPressの本番環境で使うと、&& にHTMLエンティティ変換されてJSが壊れる。三項演算子か || で代替する。上記コードの (val !== undefined ? val : 0) はそのための対処だ。


Step 4: レーダーチャートをD3.jsで描画する

レーダーチャートはD3.jsで実装する。Chart.jsではWordPressで動かなかったので。

function loadD3(callback) {
  if (typeof d3 !== 'undefined') {
    callback();
    return;
  }
  var s = document.createElement('script');
  s.src = 'https://cdn.jsdelivr.net/npm/d3@7';
  s.onload = callback;
  document.head.appendChild(s);
}

function renderRadar(scores) {
  loadD3(function() {
    const axes = Object.keys(scores);
    const n = axes.length;
    const angleSlice = (Math.PI * 2) / n;
    const radius = 150;

    // 各軸の座標を計算
    function getCoord(axisIndex, value) {
      const angle = angleSlice * axisIndex - Math.PI / 2;
      const r = (value / 100) * radius;
      return {
        x: r * Math.cos(angle),
        y: r * Math.sin(angle)
      };
    }

    // ポリゴン描画
    const points = axes.map(function(axis, i) {
      const coord = getCoord(i, scores[axis]);
      return coord.x + ',' + coord.y;
    }).join(' ');

    svg.append('polygon')
      .attr('points', points)
      .attr('fill', 'rgba(255, 137, 6, 0.3)')
      .attr('stroke', 'var(--aji-accent)')
      .attr('stroke-width', 2)
      .attr('transform', 'translate(200, 200)');
  });
}

つまずきポイント: <script src="CDN"> タグをHTMLに直書きしても、SWELLテーマはカスタムHTMLブロック内の外部スクリプトを無視する。document.createElement('script') で動的ロードするしかない。


Step 5: スマホでのタッチ対応を実装する

スマホでグラフ要素をタップしたとき、ツールチップが画面外に出て読めない問題が起きた。

画面下部に固定パネルを作って表示する方法に変えた。

function showMobilePanel(title, score, detail) {
  var panel = document.querySelector('.aji-ai-security-risk-check-mobile-panel');
  if (!panel) return;
  panel.querySelector('.panel-title').textContent = title;
  panel.querySelector('.panel-score').textContent = score + 'pt';
  panel.querySelector('.panel-detail').textContent = detail;
  panel.style.display = 'block';
}

// タッチイベントに stopPropagation を必ず付ける
radarPolygon.addEventListener('touchstart', function(e) {
  e.preventDefault();
  e.stopPropagation(); // これがないとパネルが開いて即閉じる
  showMobilePanel(axisLabel, score, actionText);
});

つまずきポイント: stopPropagation() を付けないと、touchstartがdocumentまで伝播して「パネル外タップ=閉じる」の判定が誤発火する。必ずセットで書く。


Step 6: 結果のランク判定と改善アクションを表示する

総合スコアからランクを判定し、軸ごとの改善アクションを表示する。

function getOverallRank(totalScore) {
  if (totalScore >= 85) return { rank: 'A', label: '先進レベル' };
  if (totalScore >= 68) return { rank: 'B', label: '標準レベル' };
  if (totalScore >= 52) return { rank: 'C', label: '要注意' };
  if (totalScore >= 35) return { rank: 'D', label: '危険' };
  return { rank: 'E', label: '緊急対応必要' };
}

// 軸別の改善アクション(スコアが50以下の軸に表示)
const ACTIONS = {
  data: 'AI利用時の入力禁止事項(個人情報・社内機密等)のポリシーを文書化し、全従業員に周知する',
  model: 'LLM APIのアクセスキーをチームで共有せず、個人ごとに発行して定期ローテーションを実施する',
  operation: '生成AIの出力を重要判断に使う前に、必ず人間がファクトチェックするルールを定める',
  governance: 'AIの利用目的・禁止事項・責任者を明記したAI利用ポリシーを1枚紙で策定する',
  supply: '外部LLMサービス(ChatGPT、Claude等)の利用規約のデータ取り扱い条項を確認・記録する',
  compliance: 'AI関連インシデント発生時の報告先・封じ込め手順・復旧ステップを1ページにまとめる'
};

ポイント: 改善アクションは「今日から始められる最初の一手」に限定した。「完全なガバナンス体制を構築する」ではなく「1枚紙でポリシーを作る」という具体性が大切だ。


つまずきポイントまとめ

  • && 演算子: WordPressで &#038;&#038; に変換されてJSが壊れる。三項演算子か || を使う
  • <script src>: SWELLテーマのカスタムHTMLブロックは外部スクリプトを無視する。createElement('script') で動的ロード
  • スマホのツールチップ: マウス追従型は画面外に出る。下部固定パネル方式に変える
  • stopPropagation() の忘れ: タッチイベントのバブリングでパネルが即閉じする。e.stopPropagation() を必ず書く
  • 設問の順序: フレームワークの番号順と「答えやすい順序」は別物。ユーザー視点で並べ直す

引用・出典

  • OWASP「LLM Top 10 2025」2025年公表(https://owasp.org/www-project-top-10-for-large-language-model-applications/)
  • NIST「AI Risk Management Framework 1.0」2023年1月26日
  • 総務省「AIのセキュリティ確保のための技術的対策に係るガイドライン」2026年3月27日
  • Practical DevSecOps「AI Security Statistics 2026」2026年公表
  • Accenture「Cyber Resilience 2025」2025年7月28日
1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?