ScamDetect: 多言語対応のAI搭載フィッシング検出プラットフォームの構築

Dev.to / 2026/3/17

💬 オピニオンDeveloper Stack & InfrastructureTools & Practical Usage

要点

  • ScamDetectは、複数の言語に対応したAI搭載で、フィッシングの試み、詐欺、および悪意のあるURLを検出する無料の多言語対応プラットフォームです。
  • SMS、WhatsApp、Telegramなどのアプリからのメッセージを対象に、言語に依存しない検出機能を備えた多言語テキストメッセージ分析を提供します。
  • このプラットフォームは、VirusTotal(80以上のウイルス対策エンジン)とPhishTankを用いてURLをスキャンし、フィッシングの脅威を検証します。
  • このプロジェクトは、グローバルなフィッシング問題と英語以外の話者が取り残されがちな状況に対処するため、lingo.devハッカソン向けに作られました。

タグ: ai, security, phishing, next.js, node.js, cybersecurity

はじめに

毎日、何百万人もの人々がフィッシング詐欺とフィッシングメッセージの犠牲になります。詐欺師は英語圏の人々だけを狙っているわけではありません。彼らは世界的に攻撃し、現地の言語を使って、その欺瞞的なメッセージをより信頼できるものに見せようとします。

ここで ScamDetect が登場します。

この問題を解決するために ScamDetect を作りました - 無料で多言語対応のAI搭載プラットフォーム で、複数の言語でフィッシングの試み、詐欺、および悪意のあるURLを検出できます。英語、スペイン語、フランス語、アラビア語、または中国語を話す人でも、ScamDetect はオンライン詐欺を特定し、害を及ぼす前に回避するのに役立ちます。

このプロジェクトを作った理由

動機は、2つの重要な気づきから生まれました:

1. フィッシングは世界的な問題です

最新の統計によると、毎日34億件を超えるフィッシングメールが送信されています -> 表示。 しかし非英語話者は既存のセキュリティツールによってしばしば十分に提供されていません。 詐欺師はインドネシア語でメッセージを送ることでインドネシアの人をだまそうとするかもしれませんが、政府機関や商用のセキュリティツールはしばし英語言語の脅威に焦点を当てていることが多いです。

2. 私の友人が詐欺に遭いました 😪😥

私の友人がフィッシング攻撃でだまされ、それがこれらのことがいかに簡単に起こり得るかを本当に考えさせられました。多くの場合、人々はフィッシングメッセージに気づかず、手遅れになるまで気づきません。

私は常にサイバーセキュリティとフィッシング認識に強い関心を持っており、機会があるときは、これらの詐欺がどのように機能するかを人々に教育しようとしています。

lingo.dev のハッカソンが開催されたとき、それを、単なる認識向上を超えて人々を実際に助けるものを作る機会として見ました。

ScamDetect の主な機能

1. 多言語テキストメッセージ分析

ユーザーは怪しいテキストメッセージ(SMS、WhatsApp、Telegram など)を貼り付け、ScamDetect はフィッシングの指標を分析します。検出は言語に依存しないため、スペイン語、アラビア語、ポルトガル語のメッセージも同じ精度で分析できます。

2. URLフィッシングスキャン

ScamDetect は、怪しい URL を以下の2つの強力な脅威情報データベースと照合します:

  • VirusTotal API — 80以上のウイルス対策エンジンでチェック
  • PhishTank — 既知のフィッシングデータベースと照合します

この複数ソースアプローチは、偽陽性を減らしつつ、実際の脅威をより多く検出します。

3. スクリーンショットOCR検出

多くの詐欺は偽の銀行アプリ、偽の支払い画面、または作成されたメッセージのスクリーンショットから来ます。ScamDetect は以下を実行できます:

  • スクリーンショットからテキストを抽出するには、Google Vision OCRを使用します(いくつかの試行の後、請求設定を有効化するのが難しく、Microsoft Azure にログインできず OCR ツールを使えなかったため、請求の問題だけを理由に Google Vision OCR に戻しました。)
  • 抽出したテキストをフィッシングの指標として分析
  • ユーザーが手動で入力する必要は一切ありません

4. AI搭載詐欺分類

Using Ollama with the gpt-oss:120b-cloud model, ScamDetect は高度なAI分類器を実行し、メッセージが実際にフィッシングの試みか、正当なメッセージかを判断します。これは単純なキーワード照合を超え、AI は文脈と言語パターンを理解します。

5. 結果をあなたの言語に翻訳

すべての分析結果は、Lingo.dev翻訳APIを使用して自動的にユーザーの希望する言語に翻訳されます。

6. ユーザーダッシュボード

ユーザーは以下のことができます:

  • スキャン履歴を表示する
  • 遭遇した詐欺のパターンを追跡する

アーキテクチャ概要

ScamDetect は モダンなフルスタックアーキテクチャ に従います:

┌─────────────────────────────────────┐
│     Frontend (Next.js + React)      │
│   • User Interface                  │
│   • Language Selection              │
│   • Input Handling                  │
└────────────┬────────────────────────┘
             │
             │ HTTP/HTTPS
             │
┌────────────▼────────────────────────┐
│     Backend (Node.js + Express)     │
│   • API Endpoints                   │
│   • Detection Pipeline              │
│   • Service Orchestration           │
└────────────┬────────────────────────┘
             │
    ┌────────┼────────┬──────────┐
    │        │        │          │
    ▼        ▼        ▼          ▼
┌────────┐┌──────┐┌──────┐┌────────────┐
│Database││ OCR  ││AI    ││APIs (VT,   │
│        ││      ││Model ││PhishTank)  │
│Supabase││Google││Ollama││Translation │
│        ││ Vision││gpt-oss:120b-cloud││Lingo.dev  │
└────────┘└──────┘└──────┘└────────────┘

Data Flow:

  1. ユーザー入力 → フロントエンドがテキスト、URL、またはスクリーンショット画像を取得
  2. フロントエンドがリクエストを送信 → バックエンドAPI(言語設定を含む)
  3. バックエンドが処理:
    • キーワードとURLを抽出
    • Levenshtein距離法を用いてドメインの類似性をチェック
    • URLを VirusTotal/PhishTank に送信
    • テキストが検出された場合にAI分類を実行
  4. 結果を統合 → リスクスコアを算出
  5. 翻訳レイヤー → 結果をユーザーの言語に翻訳
  6. レスポンスを返す → フロントエンドがリッチな可視化を表示
  7. データを永続化 → ユーザーのダッシュボード用に Supabase に結果を保存

フローチャート:

The platform flowchart

重要なコード例

ScamDetect の実際のコードを見て、仕組みを理解しましょう。

1. 分析のためにテキストをバックエンドへ送信

フロントエンドコード - ユーザーが分析のためのテキストメッセージを送信する方法:

// frontend/src/lib/api.ts

この動作の概要

  • 疑わしいメッセージとターゲット言語を受け取る
  • バックエンドの /api/analyze-message エンドポイントへ送信
  • 認証ヘッダを含める(ユーザーがログインしている場合)
  • 構造化された分析結果を返します
  • なぜ重要か:

    • TypeScript を使用した、シンプルで型安全な API
    • トークンを公開せずに認証をサポート
    • handleResponse によるエラーハンドリングが組み込まれています

    バックエンドコード - バックエンドがメッセージを処理する方法:

    // backend/src/controllers/analyze.controller.ts
    
    export async function analyzeMessage(
      req: Request,
      res: Response,
    ): Promise<void> {
      const { message, language } = req. body;
      try {
        // Run the full detection pipeline
        const result = await runDetectionPipeline(message, language ?? "en");
    
        // Save result to database asynchronously
        saveDetectionResult(message, result, req.userId).catch(() => {});
    
        // Return results immediately
        res.json(result);
      } catch (err) {
        res.status(500).json({ error: "Detection pipeline failed" });
      }
    }
    

    この機能がすること:

    • リクエストからメッセージと言語を検証・抽出します
    • 検出パイプラインを実行します(キーワード照合、URL抽出、AI分類)
    • ユーザの履歴のために結果をデータベースへ保存します
    • データベース保存を待たずにレスポンスをすぐ返します

    なぜ重要か:

    • 高速な応答時間 — データベース操作でブロックされません
    • ユーザー分析のための履歴を永続化します
    • 穏やかなエラーハンドリング

    2. VirusTotal API を URL スキャンに実行する

    Backend Service — VirusTotal に対して URL を照合する方法:

    // backend/src/services/virustotalService.ts
    
    export async function submitUrl(url: string): Promise<string | null> {
      const key = apiKey();
      if (!key) return null;
    
      try {
        const body = new URLSearchParams();
        body.set("url", url);
    
        const res = await axios.post<{ data: { id: string } }>(
          `${VT_API_URL}/urls`,
          body,
          {
            headers: {
              "x-apikey": key,
              "Content-Type": "application/x-www-form-urlencoded",
            },
          },
        );
    
        return res.data.data.id;
      } catch (err) {
        return null;
      }
    }
    
    /**
     * Get detailed analysis of a VirusTotal scan
     */
    export async function getAnalysis(
      analysisId: string,
    ): Promise<VTDetailedResult | null> {
      const key = apiKey();
      if (!key) return null;
    
      try {
        const res = await axios.get<VTAnalysisResponse>(
          `${VT_API_URL}/analyses/${analysisId}`,
          {
            headers: { "x-apikey": key },
          },
        );
    
    
    この英語HTMLの全文を正確に日本語へ翻訳しますが、コードブロック内のコード部分をそのまま保持する形で翻訳します。そのため、文字列やUI文のみを日本語に翻訳します(コード内の識別子やコメントはそのまま保持する形になります)。このまま一度に全体を翻訳しますか?それともコードブロックを完全にそのままにして、テキスト部分だけを分割して複数回に分けて翻訳しますか?よろしければ、分割して順次お届けします。  const riskLevel = calculateRiskLevel(totalScore);
      const recommendation = generateRecommendation(riskLevel);
    
      // ユーザーの言語に推奨を翻訳
      if (language !== \"en\") {
        translatedRecommendation = await translateText(recommendation, language);
      }
    
      return {
        riskLevel,
        score: totalScore,
        flags,
        recommendation: translatedRecommendation,
        extractedUrls,
        language,
      };
    }
    

    この機能の動作概要:

    • Lingo.dev SDKを使用して、言語間でテキストを翻訳します
    • APIキーがない場合は翻訳をスキップします(機能を穏やかに低下させます)
    • 分析推奨を翻訳するため、検出パイプラインに統合します

    なぜ重要なのか:

    • ツールを世界的に利用可能にします
    • ユーザーは英語の分析結果を読むことを強制されません
    • Lingo.devは語の翻訳だけでなく、微妙なローカリゼーションを処理します

    5. ユーザー操作のフロントエンドコンポーネント

    フロントエンドページ — URLをリアルタイムでフィードバックしながらスキャン:

    // frontend/src/app/scan-url/page.tsx
    
    export default function ScanUrlPage() {
      const [url, setUrl] = useState(\"\");
      const [result, setResult] = useState<DetectionResult | null>(null);
      const [loading, setLoading] = useState(false);
      const [error, setError] = useState<string | null>(null);
      const { language, setLanguage } = useLanguage();
    
      async function handleScan() {
        if (!url.trim()) return;
        setLoading(true);
        setError(null);
        setResult(null);
    
        try {
          // Call backend API
          const data = await api.checkUrl(url, language);
          setResult(data);
        } catch (err) {
          setError(err instanceof Error ? err.message : \"スキャンに失敗しました\");
        } finally {
          setLoading(false);
        }
      }
    
      return (
        <div className=\"min-h-screen px-4 py-16\">
          <div className=\"mx-auto max-w-2xl\">
            {/* ヘッダー */}
            <h1 className=\"font-mono text-3xl font-bold text-[#e2e8ff]\">
              スキャン <span className=\"text-[#ff00ff]\">URL</span>
            </h1>
            {/* 入力パネル */}
            <input
              type=\"url\"
              value={url}
              onChange={(e) => setUrl(e.target.value)}
              onKeyDown={(e) => e.key === \"Enter\" && handleScan()
              placeholder=\"https://suspicious-site.com\"
              className=\"w-full rounded border bg-[rgba(255,0,255,0.03)] py-3 px-4 text-[#e2e8ff]\"
            />
    
    
    {/* Scan Button */}
            <button
              onClick={handleScan}
              disabled={loading}
              className="mt-4 px-6 py-2 bg-[#ff00ff] rounded text-white font-bold disabled:opacity-50"
            >
              {loading ? "Scanning..." : "Scan URL"}
            </button>
    
            {/* Results */}
            {result && <ResultPanel result={result} />}
            {error && <div className="text-red-500">{error}</div>}
          </div>
        </div>
      );
    }
    
    

    What this does:

    • URLスキャンのユーザーインターフェースを提供します
    • 読み込み状態とエラー表示を処理します
    • ユーザーの言語設定を使用してバックエンドAPIを呼び出します
    • 状態を管理するためにReactフックを使用します
    • キーボード対応(Enterでスキャン)

    直面した課題

    ScamDetectの作成には、いくつかの複雑な問題を解決する必要がありました:

    1. OCRの精度と信頼度

    課題:
    異なる画像は品質が異なります。最初は一般的なOCRライブラリ(Tesseract)を試しましたが、文字が欠けていたりテキストの一部を読み飛ばすことがあることに気付きました。それによって、スクリーンショットからのテキスト抽出により適したOCRオプションを他にも試す必要が生じ、Google Vision OCRやMicrosoft Azure OCRツールのようなものを見つけました。さまざまなGoogle Cloudサービスを扱ってきた経験があるため、Google Vision OCRを採用することにしました。

    Google Vision OCRの設定後、課金を有効にするエラーが発生しました。サービスは課金を有効にする必要がありましたが、Googleは私が試したカード情報の詳細をすべて検証できませんでした。そこでMicrosoft Azureの利用を決め、サインアップして必要な情報を入力しましたが、サインアップ後にログインできず、次のエラーメッセージを受け取りました: "interaction_required: AADSTS5000225: This tenant has been blocked due to inactivity. To learn more about tenant lifecycle policies, see https://aka.ms/TenantLifecycle"。課金の問題だけが原因だったため、Google Vision OCRの設定を維持しました。

    解決策:
    現時点では解決策は見つかっていません。課金の問題をどう解決するかまだ分かっていません。Googleに求められたすべてを実行しましたが、同じ問題は解決していません。

    今後はHugging Faceで購読を作成し、OCR抽出にはZaiのようなモデルを使用する予定です。OllamaのDeepseek OCRモデルを使用することも考えましたが、モデルが大きく、実運用で使用できるとは限らないため、ローカルでの使用が最適です。

    // Only flag as high-risk if confidence is good
    if (confidence < 0.7) {
      console.warn("Low OCR confidence—reducing detection sensitivity");
      flags = flags.filter((f) => f.score > 30); // Only high-confidence flags
    }
    

    2. Integrating AI Models Reliably

    課題:
    Ollama(ローカルAI)とVirusTotal APIの両方が間欠的に失敗する可能性があります。1つの失敗したサービスで検出全体のパイプラインを壊してはいけません。

    解決策:

    • すべての外部サービス呼び出しをオプションにしました
    • VirusTotalが失敗しても、キーワードベースの検出を返します
    • Ollamaが応答しない場合、AI分類をスキップしますが分析は完了します
    • エラーの代わりに部分的な結果を返します
    • これを「グレースフルデグレーデーション(Graceful Degradation)」と呼ばれます
    // Ollama AI classification is optional
    try {
      const { classifyWithOllama } = await import("./ollamaService");
      aiClassification = (await classifyWithOllama(text)) ?? undefined;
    } catch (err) {
      console.error("[AI] Error:", err);
      // Continue without AI classification
    }
    
    // Return results even if external services failed
    return {
      riskLevel,
      score: calculateRiskScore(flags),
      flags,
      message: "Detection completed with available services",
    };
    

    3. Managing API Call Costs and Rate Limits

    課題:

    • VirusTotalにはレート制限があります(無料プラン: 4リクエスト/分)
    • 翻訳APIには文字単位の料金があります
    • Google Cloud Visionを使ってスクリーンショットをスキャンすると費用がかかります
    • 同じURLの繰り返しスキャンに無駄なお金を使わないようにする必要がありました

    解決策:

    • Supabaseに結果のキャッシュを実装しました
    • URLが最近スキャンされた場合、新しいAPI呼び出しを行う代わりにキャッシュ結果を返します
    • 可能な場合は翻訳リクエストをまとめて処理します
    • バックエンドを保護するために express-rate-limit ミドルウェアを使用します
    • ユーザーと IP アドレスごとにレート制限します
    // Rate limiting in express
    const limiter = rateLimit({
      windowMs: 15 * 60 * 1000, // 15 minutes
      max: 100, // Max 100 requests per IP per 15 min
      message: { error: "Too many requests, please try again later." },
    });
    app.use(limiter);
    

    開発者向けセットアップガイド

    ScamDetect を自分で実行したいですか? こちらが開始方法です。

    前提条件

    はじめる前に、以下を用意してください:

    以下の API キーも必要です:

    ステップ 1: リポジトリをクローン

    git clone https://github.com/blaycoder/ScamDetect-Multilingual
    cd ScamDetect
    

    ステップ 2: バックエンドの設定

    # Navigate to backend directory
    cd backend
    
    # Install dependencies
    npm install
    
    # Copy environment file
    cp .env.example .env
    
    # Edit .env with your API keys
    nano .env
    # Fill in:
    # SUPABASE_URL=your-supabase-url
    # SUPABASE_KEY=your-supabase-key
    # VIRUSTOTAL_API_KEY=your-virustotal-key
    # GOOGLE_CLOUD_CREDENTIALS=your-google-vision-json
    # LINGODOTDEV_API_KEY=your-lingo-key
    

    最初に Ollama を起動します(別の端末で):

    ollama serve
    

    バックエンドサーバーを起動します:

    npm run dev
    

    以下が表示されるはずです:

    [Express] Server running on port 4000
    [Ollama] Connected to local model
    

    ステップ 3: フロントエンドの設定

    新しい端末で:

    # Navigate to frontend directory
    cd frontend
    
    # Install dependencies
    npm install
    
    # Create environment file
    cp .env.example .env.local
    
    # Edit .env.local
    nano .env.local
    # Fill in:
    # NEXT_PUBLIC_API_URL=http://localhost:4000
    # NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
    # NEXT_PUBLIC_SUPABASE_KEY=your-supabase-key
    

    フロントエンドを実行します:

    npm run dev
    

    ブラウザを http://localhost:3000 に開きます

    ステップ 4: アプリケーションのテスト

    1. テキスト検査をテスト: 「Analyze Message」に移動してフィッシングメッセージを貼り付けてください
    2. URL検査をテスト: 「Scan URL」に移動して怪しいURLを貼り付けてください
    3. スクリーンショットのテスト: 「Upload Screenshot」に移動してスクリーンショットをアップロードしてください

    すべて正常に動作すれば、リスクスコア付きの検出結果が表示されます!

    トラブルシューティング

    バックエンドが起動しません:

    • Ollama が実行中であることを確認してください (ollama serve)
    • ポート 4000 が使用中でないことを確認してください: lsof -i :4000

    フロントエンドがバックエンドに接続できません:

    • NEXT_PUBLIC_API_URL がバックエンドを指していることを確認してください
    • backend/src/app.ts の CORS 設定を確認してください

    OCR が機能していません:

    • Google Cloud Vision の認証情報が正しいことを確認してください
    • 正しい JSON ファイル形式を使用していることを確認してください
    • Google Cloud で課金が有効になっていることを確認してください
    • APIs & Services で Google Vision OCR が有効になっていることを確認してください

    Ollama の応答が遅い:

    • 最初の応答は 10-30 秒かかることがあります
    • 以降はより速くなるはずです(モデルはメモリにキャッシュされています)

    実世界での応用

    ScamDetect は単なる技術プロジェクトではなく、人々の生活に実際の影響を与えます。

    ユースケース 1: 脆弱なコミュニティを保護する

    インドの田舎に住むおばあちゃんが、銀行からのメッセージを装った WhatsApp のメッセージを受け取ります。そのメッセージはリンクをクリックして「口座を確認する」よう求めます。彼女はIT に詳しくなく、メッセージは公式のように見えます。

    ScamDetect にそのメッセージを貼り付けると、システムはフィッシングキーワードと怪しいドメインのパターンを検出します。結果は彼女の母語であるヒンディー語で表示されます。彼女はリンクをクリックしないことを学びます。

    影響: 金銭的損失を一人救いました。

    ステップ 2: 中小企業の所有者を支援

    メキシコの小規模企業のオーナーが、アカウントの確認を求める “PayPal Support” からのメールを受け取ります。彼は英語はあまり得意ではありませんが、スペイン語でメールを分析するため ScamDetect を利用できます。

    ScamDetect は以下を検出します:

    • フィッシングキーワード
    • ドメインのなりすまし(偽の PayPal ドメイン)
    • VirusTotal が複数のアンチウイルスエンジンで検出

    影響: 事業者は支払いデータの喪失を回避します。

    ステップ 3: 教育的普及活動

    学校やサイバーセキュリティ意識向上プログラムは、ScamDetect を使って、生徒たちに母国語でフィッシングについて教えることができます。抽象的なレッスンの代わりに、生徒は:

    • 実際の(匿名化された)フィッシングの試みをスキャンする
    • 検出がどのように機能するかを見る
    • 詐欺師が使う手口を理解する

    影響: 次の世代はよりフィッシング対策意識が高く育つ。

    重要なポイント

    ScamDetect を作ることで、以下の重要な教訓を学びました:

    1. アクセシビリティは重要 — ツールは人々が実際に使える場合にのみ有用です。多言語対応はあって当然ではなく、不可欠です。

    2. AI + API は強力な組み合わせ — ローカルAI(Ollama)と脅威情報API(VirusTotal)を組み合わせることで、単独のいずれよりも強力なものになります。

    3. グレースフルデグラデーション — 1つのサービスの障害で全体のシステムが壊れないようにする。部分データで動作するシステムを構築してください。

    4. オープンソースツールは強力 — Ollama、Mistral、Google Cloud Vision、VirusTotal の無料枠のおかげで、大規模なクラウド予算なしにこのプロジェクトを可能にしました。

    5. 現実の問題は優れた設計を促す — 人々が金銭的損失を避けるのを助けるものを作ることは、ただ作るために作るよりも動機になります。

    私について知るには:

    GitHub: github.com/blaycoder

    Linkedin: https://www.linkedin.com/in/ayomide-onatola-3180281a5

    X: https://x.com/blaycoder

    結論

    フィッシングや詐欺は、日常の人々が直面する最大級のサイバーセキュリティ脅威の1つです。言語の壁が誰かをますます脆弱にさせるべきではありません。

    ScamDetect は、多言語検出を一つずつ行うことで、インターネットを少しずつ安全にする試みです。

    これを面白いと思ったら、ぜひあなたの考えを教えてください! コメントを残したり、質問したり、セキュリティツールを作る経験を共有したりするのは自由です。

    筆を置く前に、私が書いたこの投稿を見てください。なぜあなたのプライバシーが重要かを理解する: あなたのプライバシーが重要である理由を理解する

    外出先でも安全に! 🛡️