【小ネタ】標的型訓練メールに引っかかったのが悔しかったので標的型メール通知ツールをつくった

【小ネタ】標的型訓練メールに引っかかったのが悔しかったので標的型メール通知ツールをつくった

2026.06.17

せーのでございます。

クラスメソッドでは定期的に標的型訓練メールが送られてきます。Microsoft や Google、OpenAI などを名乗ったり、社長名義だったりと、かなり紛らわしい。引っかかると「引っかかりました」画面が出てアンケート記入、という流れなのですが、正直すごく悔しいです。

忙しいときは注意力が散漫になりがちで、ついリンクを踏んでしまうこともあります。毎回 Gmail で送信元アドレスを目視確認するのも現実的ではないので、GAS と Vertex AI Gemini で未読の重要メールを定期チェックし、怪しければ Slack に通知するツールを作りました。

やりたいこと

  • AI に「これ怪しくない?」を任せて、リンクを踏む前に気づきたい
  • メールは削除せず、通知だけ(誤検知を考慮)

ざっくり仕組み

Gmail(未読 + 重要)→ GAS(5〜10分トリガー)→ Gemini 2.5 Flash(JSON判定)→ Slack通知 + 既読化

業務メールを扱うので Google AI Studio の無料枠ではなく Vertex AI を使っています(無料枠はデータがモデル改善に使われる可能性があります)。認証は ScriptApp.getOAuthToken() なので、API キーの管理は不要です。

コード

PRESIDENT_NAME はうちの社内ルール用の例です。自分の環境に合わせて書き換えてください。

// --- 設定項目 ---
const PROJECT_ID = 'YOUR_GCP_PROJECT_ID'; // GCPのプロジェクトID
const LOCATION = 'asia-northeast1'; // 東京リージョン
const SLACK_WEBHOOK_URL = 'https://hooks.slack.com/services/XXXXXXXXXX';
const PRESIDENT_NAME = '横田'; // 社長名(検知ルールに使用)
const SEARCH_QUERY = 'is:unread is:important'; // 未読かつ重要ラベル
// ---------------

function checkPhishingEmails() {
  const threads = GmailApp.search(SEARCH_QUERY);
  if (threads.length === 0) return;

  for (let i = 0; i < threads.length; i++) {
    const messages = threads[i].getMessages();
    for (let j = 0; j < messages.length; j++) {
      const message = messages[j];
      
      if (message.isUnread()) {
        const sender = message.getFrom();
        const subject = message.getSubject();
        const body = message.getPlainBody();

        const analysisResult = analyzeEmailWithVertexAI(sender, subject, body);
        console.log("AIの判定結果:", analysisResult);

        if (analysisResult && analysisResult.is_phishing) {
          sendToSlack(sender, subject, analysisResult.reason);
        }

        message.markRead();
      }
    }
  }
}

function analyzeEmailWithVertexAI(sender, subject, body) {
  const url = `https://${LOCATION}-aiplatform.googleapis.com/v1/projects/${PROJECT_ID}/locations/${LOCATION}/publishers/google/models/gemini-2.5-flash:generateContent`;
  
  const prompt = `
あなたは優秀なサイバーセキュリティアナリストです。以下のメール情報から、フィッシング詐欺や標的型攻撃訓練メールであるかどうかを判定してください。

【判定ルール】
1. 送信者名や本文に「${PRESIDENT_NAME}」が含まれている場合、本人はメールを送らないため100%詐欺です。
2. Microsoft, Google, OpenAI, Anthropicなどの大手企業を名乗っているが、送信元アドレスのドメインがフリーメールや公式と異なるスペル(例: g00gle.comなど)の場合は詐欺です。
3. アンケートへの回答、パスワードの変更、緊急のリンククリックを促す内容は強く疑ってください。

【メール情報】
送信元: ${sender}
件名: ${subject}
本文: ${body.substring(0, 1000)}

【出力形式】
必ず以下のJSON形式のみを出力してください。
{
  "is_phishing": true または false,
  "reason": "判定理由を簡潔に"
}
`;

  const payload = {
    contents: [{ role: "user", parts: [{ text: prompt }] }],
    generationConfig: { responseMimeType: "application/json" }
  };

  const options = {
    method: 'post',
    contentType: 'application/json',
    headers: {
      'Authorization': 'Bearer ' + ScriptApp.getOAuthToken()
    },
    payload: JSON.stringify(payload),
    muteHttpExceptions: true
  };

  try {
    const response = UrlFetchApp.fetch(url, options);
    const json = JSON.parse(response.getContentText());
    if (json.candidates && json.candidates.length > 0) {
      const resultText = json.candidates[0].content.parts[0].text;
      return JSON.parse(resultText);
    } else {
      console.error("API Error: " + response.getContentText());
    }
  } catch (e) {
    console.error("Fetch Error: " + e);
  }
  return null;
}

function sendToSlack(sender, subject, reason) {
  const payload = {
    text: `🚨 *不審なメール(詐欺・訓練の可能性)を検知しました* 🚨\n*送信元:* ${sender}\n*件名:* ${subject}\n*AIの判定理由:* ${reason}\n\nうっかりリンクを踏まないように注意してください!`
  };

  const options = {
    method: 'post',
    contentType: 'application/json',
    payload: JSON.stringify(payload)
  };

  UrlFetchApp.fetch(SLACK_WEBHOOK_URL, options);
}

appsscript.jsoncloud-platform スコープが必要です。

{
  "timeZone": "Asia/Tokyo",
  "dependencies": {},
  "exceptionLogging": "STACKDRIVER",
  "runtimeVersion": "V8",
  "oauthScopes": [
    "https://www.googleapis.com/auth/script.external_request",
    "https://www.googleapis.com/auth/gmail.modify",
    "https://www.googleapis.com/auth/cloud-platform"
  ]
}

これを設定して5分間隔で回すだけです。
ちなみに過去に送られてきた標的型訓練メールを未読に戻してテストしてみました。

fishing_mail1.png

うん、ばっちし。

設定メモ

コピペしたあと、だいたい次だけやれば動きます。

  • GCP プロジェクトを作成し、Vertex AI API を有効化
  • GAS の「プロジェクト設定」で GCP プロジェクト番号を紐づけ
  • スクリプト実行者に Vertex AI ユーザー ロールを付与(これを忘れると 403 になります)
  • Slack Incoming Webhook URL を SLACK_WEBHOOK_URL に設定
  • トリガーで checkPhishingEmails を 5〜10 分間隔で実行

試すだけなら特に問題ありません。同じように訓練メールにうっかり引っかかりがちな方、みなさんはどう対策していますか?

参考資料


国内企業 AI活用実態調査2026 配布中

クラスメソッドが独自に行なったAI診断調査をもとに、企業のAI活用の現在地を調査レポートとしてまとめました。企業規模別の活用度傾向に加え、規模を超えてAI活用を進める企業に共通する取り組みまで、自社の現在地を捉えるためのヒントにぜひ。

国内企業 AI活用実態調査2026

無料でダウンロードする

この記事をシェアする

関連記事