個人開発した情報漏えいダッシュボード「SecuMiru(セキュミル)」の後編です。前編では、公式発表で確認できたものだけを正しく数えるためのデータ設計を書きました。後編では、毎日の情報をどう集めているか、その中で AI に何を任せて何を任せていないか、そして運用で一番苦労した「公式発表が消える」問題についてまとめます。
検証環境
- OS: macOS 26.7.1 / Node.js v26.12.1
- React 19.3 / Vite 8.3.4 / TypeScript / zod 4.6
- 自動化: GitHub Actions / ホスティング: Cloudflare
- 下書きに使う AI: Gemini または OpenAI(いずれも検索機能つき)
- 数値は 2026年10月25日時点
全体の流れ
インシデントのニュースは毎日出ます。すべてを手で追うのは無理なので、次の流れにしています。
- 収集(毎朝 6 時): GitHub Actions が、公的機関・業界団体・報道の 8 つのフィードから候補を集め、Issue にまとめる。この時点ではデータを一切変更しない
- 下書き: AI が候補を公式発表と照合し、データの下書きを作る。確認できなかった候補は「保留」として理由を残す
- 検証: 下書きをスキーマ検証と出典 URL の到達確認にかけ、通ったものだけを下書き PR にする
- 確認と公開: 筆者が PR を見て、公式発表の原文を開いて確かめてからマージし、公開状態に切り替える
フィードは JPCERT/CC と IPA の新着、フィッシング対策協議会、セキュリティ系のニュースサイトなどです。報道は事例を見つける手掛かりとして使い、登録の根拠にはしません(前編の方針)。
AI に書き込み権限を渡さない
下書きを作る AI は、検索で公式発表を探して内容を読み、データを組み立てます。便利な反面、存在しない URL や数字をもっともらしく作る可能性があります。そこで、AI が直接リポジトリを書き換えられない構成にしました。
GitHub Actions のワークフローを 2 つのジョブに分けています。AI を呼ぶ draft ジョブには読み取り権限しか与えず、結果はファイル(成果物)として出力するだけです。書き込み権限を持つ publish ジョブがその成果物を受け取り、もう一度検証してから、下書き状態の PR を作ります。AI が出したものは、必ず検証と人の確認を通らないと公開されません。
照合に使う AI は、最初は Claude でしたが、10月23日に検索機能つきの Gemini / OpenAI に切り替えました。プロバイダーとモデルは、GitHub の設定値を変えるだけで差し替えられるようにしています。
検証に落ちたら、理由を返して作り直させる
AI の出力は、そのまま使える形で返ってくるとは限りません。下書きのスクリプトでは、出力を検証し、通らなければ何が駄目だったかを AI に返して、もう一度作らせるようにしています(1 件につき最大 2 回)。実際のコードの一部です。
const parsed = incidentSchema.safeParse(rec);
if (!parsed.success) { feedback = parsed.error.issues.map((i) => `${i.path.join('.')} ${i.message}`).join('\n'); continue; }
if (!parsed.data.sources.some((s) => s.sourceType !== 'secondary')) { feedback = '公式発表(official / public_agency)の出典が必要です'; continue; }
const dead = [];
for (const s of srcs) if (!(await urlReachable(s.url))) dead.push(s.url);
if (dead.length) { feedback = `次の出典URLに到達できません。実在する公式ページのURLに直してください:\n${dead.join('\n')}`; continue; }スキーマに合わない、公式発表の出典が無い、出典の URL に到達できない、のいずれかなら、その理由を次の問い合わせに添えます。2 回で通らなければ、その候補は「検証を通せませんでした」として保留にします。新しく作られたデータは必ず下書き状態で、レビュー日時も空にしておき、人の確認を経ずに公開状態にはなりません。
それでも誤りは出る
AI の照合に加えて、筆者も PR で原文を開いて確かめています。それでも誤りは出ました。v1.1.0 の作業では、過去のレビュー指摘を受けて 9 件の事例を訂正しています。
- 漏えいの状況を、原文の表現に合わせて「確認済み」や「可能性あり」に直した
- 影響規模の単位を「人」と書いていたが、原文は「件」だった
- 「停止はなかった」と公表された事例のサービス状況を「復旧」と書いていたので、「サービスへの影響なし」に直した
- パスワードについて「すべて無効化済み」と書いていたが、原文は「順次無効化し、ログイン時に再設定」だった
どれも、原文の細かい言い回しの違いでした。訂正した内容は各事例の訂正履歴に残し、サイト上でも公開しています。訂正した事例は更新日を訂正日に変え、新着フィードとサイトマップにも反映しています。照合を AI と人で重ねても誤りはゼロにならない前提で、直した跡を残せる作りにしてあります。
一番苦労したこと:公式発表が消える
運用で一番苦労したのが、公式発表のページが消えることです。インシデントのお知らせは、時間が経つと企業のサイトから削除されたり、サイトのリニューアルで URL が変わったりします。根拠を公式発表に限っている以上、出典が消えるのは致命的です。
出典が消えた事例では、Internet Archive に保存された版に差し替えています。2026年10月25日時点で、8 件の事例が計 35 本の保存版の URL を出典に使っています。保存版も残っていない場合は、その事例を公開できません。実際に「公式ページが 404 で、保存版も無い」ため保留にした事例があります。
リンク切れ検査は「何を失敗にするか」が難しい
出典の消失にいち早く気づくため、毎週月曜に全出典のリンク切れ検査を GitHub Actions で回しています。ここで、何を「リンク切れ」とみなすかを 2 回調整しました。
最初に、アクセス拒否や一時的なサーバーエラーを警告に分けました。CI のサーバーからのアクセスを拒否するサイトや、一時的に落ちているサイトがあるためです。さらにレビューの指摘を受けて、失敗にするのは HTTP 404・410 だけに絞りました。
もう一つの工夫が、偽の 404 への対処です。一部のサイトは、ボットらしいアクセスや CI のサーバーからのアクセスに 404 を返します。そこで 404 のときだけ、ブラウザと同じようなヘッダーを付けてもう一度確かめています。検査スクリプトの判定は次のとおりです(一部抜粋)。
const BROKEN_STATUSES = new Set([404, 410]);
// 404/410=リンク切れ(失敗)。それ以外の応答と接続エラーは警告(原文のコメントを要約)
if ([401, 403, 429, 999].includes(res.status)) return { kind: 'warn', detail: `HTTP ${res.status}(アクセス拒否の可能性。手元で確認)` };
if (res.status >= 500) return { kind: 'warn', detail: `HTTP ${res.status}(サーバー側の一時的な応答の可能性。手元で確認)` };警告になったものは、手元のブラウザで開いて確かめます。
まとめ
- 収集は毎朝自動で行い、データを変えるのは人が確認してマージしたときだけにする
- AI には書き込み権限を渡さず、出力は検証と人の確認を通ったものだけを公開する
- 検証に落ちた理由を AI に返して作り直させると、手直しの手間が減る
- それでも誤りは出る。訂正履歴を残し、更新日・フィード・サイトマップに反映できる作りにする
- 公式発表は消える。保存版への差し替えと、リンク切れ検査の判定の調整が欠かせない
AI の検索機能は Gemini API のドキュメント、ワークフローの権限の指定は GitHub Docs を参照しました。同じく AI エージェントと進めた作業として、「microCMS × Next.js の運用でハマった8つの落とし穴」も書いています。



