情報漏洩の記事をAIで全自動公開しようとして、1日でやめた理由

収集から公開までを全自動化する設計が「推測や二次情報を載せない」原則と衝突。公式PDFやJS描画ページから一次情報を抽出し、人が確認して公開する半自動フローへ転換した記録。

#設計#AI#スクレイピング

企業の個人情報漏洩事件を、時系列・原因・対策の3つの視点から構造化して学べるデータベースサイト「漏洩百貨店(loo100 / 旧SecuTreNews)」を開発していたときのことです。

企画の最初期、私は「ネット上のニュースや企業リリースをAIが自動収集し、記事を生成・検証して、人間の手を介さずに全自動で公開し続けるパイプライン」を設計していました(決定事項 D-005)。しかし、その設計書を書き終えたわずか24時間後、私は全自動公開の仕組みを完全に破棄し、**「AIは下調べと下書きの支援に徹し、一次情報の確認と公開判断は必ず人間が行う」**という半自動ワークフロー(D-008)へ180度舵を切ることになりました。

なぜ全自動公開を1日でやめなければならなかったのか。そこには、情報漏洩というテーマ特有の責任の重さと、企業の公式発表が持つ泥臭い現実、そして一次情報を守るための技術的な戦いがありました。

最初に描いた「夢の全自動パイプライン」

開発初日(2026年10月19日)、私が design/pipeline.md に描いていたパイプラインは、次のような7つのステージで構成されていました。

[収集] ニュースRSSや適時開示・プレスリリースを定期巡回
  ↓
[判定] 情報漏洩・不正アクセスに該当するかをLLMが判定
  ↓
[名寄せ] 過去の事件との重複や、第1報・第2報の関連付け
  ↓
[一次情報取得] 企業の公式PDFや告知ページをクローリング
  ↓
[生成] 時系列・原因・対策・影響規模を構造化Markdownに変換
  ↓
[自動検証] 24項目のチェックリストでハルシネーションや不備を検査
  ↓
[自動公開] テストに合格した記事をGitコミットまたはDB登録して即時デプロイ

このシステムを支える前提として、私は「完全自動化だからこそ、人間の編集者以上に厳格なセーフティネットを敷く」と考え、24項目の自動検証チェックリストを定義しました。「出典リンクが実在するか」「漏洩件数に根拠のない数字が含まれていないか」「推測と事実が混ざっていないか」などをスクリプトとLLMで二重に検証し、1項目でも引っかかれば公開を自動で止める、という設計です。

コードを書く前は、「これだけ鉄壁のガードレールを組めば、無人で回る高品質なセキュリティメディアが作れるはずだ」と信じ切っていました。

なぜ1日で破綻したのか:一次情報の泥臭い現実

しかし、実際に直近で話題になっていた実在の事件(タイムズカーの不正アクセスによる約660万件のアカウント情報流出など)をサンプルにパイプラインをシミュレーションした瞬間、この構想はガラガラと音を立てて崩れ去りました。

原因は、最初に掲げた**「推測や噂を一切排除し、一次情報(公式発表)のみを根拠にする」という最上位原則(D-002)**と、全自動化が本質的に相容れなかったことにあります。

理想としていた自動化企業発表の現場の現実
構造化されたHTMLプレスリリースを取得できるお知らせが「画像化されたPDF」や「複数ページのスライド」でしか配られない
URLを叩けば誰でもいつでも本文テキストが取れるJavaScriptで動的レンダリングされるページや、古いCMSのShift_JISページが頻発する
1本の記事で確定情報が揃う「第1報:調査中」→「第2報:一部流出確認」→「最終報:件数確定」と数ヶ月かけて数字が書き換わる
LLMが「事実」と「推測」を自律判定できる「外部からの不正アクセスを受けた可能性がある」という企業側の婉曲表現を「流出確定」と断定してしまう

情報漏洩の発表資料は、Webスクレイピングにとって最も厄介なフォーマットの宝庫です。セキュリティ事故を起こした企業のお知らせページは、時に文字のコピーを防ぐためかテキスト情報を含まない画像PDFとして貼り付けられていたり、動的なポータルサイトの奥深くに埋め込まれていたりします。通常のHTTPリクエスト(fetch)では空のHTMLしか返ってこず、PlaywrightやヘッドレスChromeを立ち上げてレンダリングを待ち、PDF解析ライブラリでテキストを抽出しなければ、一次情報の入り口にすら立てません。

さらに致命的だったのは、**「数字の移り変わり」**でした。情報漏洩は初動では「最大で〇〇件の可能性」と発表され、フォレンジック調査の進展に伴って対象件数が数百件から数百万件へと跳ね上がったり、逆に「調査の結果、外部流出の痕跡なし」と結論づけられたりします。

この繊細な修正のグラデーションをAIが全自動で名寄せして記事を上書きしていくと、企業の発表の意図を誤読したり、二次報道メディアの刺激的な見出し(推測)を「事実」として取り込んでしまう事故が防ぎきれません。もし誤った数字や不正確な原因を自動公開してしまえば、被害を受けた企業に対してさらなる名誉毀損や風評被害を与える「加害者」にこちらがなってしまいます。

「AIの判断が1%でも狂えば、人の名誉やセキュリティ対策の現場を傷つける。それを防ぐ最後の砦を、コードの自動検証だけに委ねてはならない」

そう痛感した私は、わずか1日でD-005を廃止し、D-008(AIは一次情報の収集・要約支援に徹し、公開は人間が確認して行う)へと舵を切りました。

新しいワークフロー:一次情報からGit管理のMarkdownへ

全自動を捨てたことで、システムのアーキテクチャは劇的にシンプルで堅牢なものに生まれ変わりました。

当初は自動パイプラインが更新する前提だったため、記事データを Cloudflare D1(リレーショナルデータベース)のテーブルに格納する予定でした。しかし、人間が確認して公開する運用にするなら、データベースに記事本文を持つ必要はありません。記事をすべてGitリポジトリ内のMarkdownファイル(src/content/incidents/*.md)として管理する静的サイト(SSG)構成に切り替えたのです。

新しく確立した記事作成フローは次のとおりです。

  1. 起点と一次情報の特定: 話題の事件について、企業の公式サイトからPDFやニュースリリースの一次情報を収集する。JavaScriptレンダリングが必要なページはヘッドレスChromeでDOMを取り出し、PDFはテキスト抽出にかける。
  2. AIによる下書きの構造化: 一次情報テキストをインプットとして渡し、時系列(timeline)、原因(cause)、対策(countermeasures)、影響範囲(impact)の統一スキーマに沿ったMarkdownを生成する。
  3. 人間による照合作業: 記事内のすべての記述が、添付された出典URL(一次情報)のどこに書かれているかを人間が1行ずつ突き合わせる。出典に書かれていない推測や所感はすべて削除する。
  4. Gitコミットによる公開: 検証を通過したMarkdownファイルを main ブランチへコミットし、Cloudflare Workers Builds が静的HTMLとして自動デプロイする。

記事がGitで管理されるようになったことで、「いつ、誰が、どの公式発表を元に数字を修正したのか」の変更履歴がコミットログとして完全に追跡できるようになりました。データベースの不整合や意図しない上書き事故に怯える必要もなくなりました。

Astro 7 + Hono + Cloudflare Workers で踏んだ本番ビルドの罠

アーキテクチャを静的サイトへと移行するにあたり、フレームワークには Astro 7 + Hono を選びました。記事の本文や歴史のグラフは事前生成された静的HTMLで超高速に配信し、後述する140文字の匿名コメント欄だけを Hono による軽量API(Cloudflare D1接続)で受けるハイブリッド構成です。

しかし、この構成を本番向けにビルドした際、奇妙なバグに直面しました。

開発サーバー(astro dev)では何の問題もなく動いていたコメント投稿APIが、本番ビルド(astro build)をプレビューした途端、すべての /api/* リクエストに対して無情な404 Not Foundを返すようになったのです。

原因を調査したところ、Astro 7 の Cloudflare アダプターが持つ最適化の挙動にありました。当初、私はサイト全体の統一エントリーポイントとして src/fetch.ts に Hono アプリをマウントする方式をとっていました。しかし、記事ページや一覧ページがすべて prerender = true(完全な静的書き出し)になっていたため、Astroのビルダーが「このサイトにはサーバーサイドで動かすオンデマンドの処理が一切存在しない」と誤認し、Cloudflare Workers のスクリプト(_worker.js)を出力せずに純粋な静的ファイル群だけを書き出していたのです。

結果として、静的ファイルの中に存在しない /api/... はただの存在しないパスとして404になっていました。

この問題は、エントリーポイントの配置を src/fetch.ts からオンデマンドルーティングを明示する src/pages/api/[...path].ts へと戻し、Cloudflare の環境変数バインディングを @astrojs/cloudflare のミドルウェア任せにせず cloudflare:workers の env から直接受け取って Hono に渡す形に修正したことで解決しました。

匿名コメント欄の防衛:回避表記(hxxp, [.])まで弾く設計

記事を静的に配信する一方で、読者がセキュリティの学びや気づきを共有できるよう、各事件の詳細ページには匿名コメント欄(F-11)を設けました。しかし、情報漏洩サイトのコメント欄は、放っておけば二次流出データの貼り付け場所や攻撃的な憶測の温床になりかねません。

ここでも「D-002(漏洩データや噂を載せない)」の原則を守るため、徹底した防御策を講じました。

  • 140文字以内の短文制限: 長大なリークデータやコードの貼り付けを物理的に不可能にする
  • リンクの完全禁止と回避表記の検出: 通常の http:// や https:// だけでなく、セキュリティ業界やアンダーグラウンドでURL共有を偽装する際によく使われる hxxp や [.]、ドメイン名形式(example[.]com)の表記まで正規表現で検出し、サーバー側で400エラーとして拒否する
  • Cloudflare Turnstile によるbot遮断: 人間による投稿のみを許可し、スパムの自動投下を防ぐ
  • 連続投稿制限: 同一IPからの投稿を5分間に3件までに制限し、荒らし行為を抑止する
  • プレーンテキスト強制: HTMLタグはすべてエスケープし、自動リンク化も行わない(XSSの完全防止)

さらに、投稿されたコメントは即時公開されるものの、管理者が後から点検できるモデレーションフロー(development/comment-moderation.md)を用意し、問題のある投稿は管理者がワンコマンドで削除できる体制を整えました。

「自動化できること」と「自動化してはいけないこと」

開発を終えて振り返ると、全自動公開の夢を1日で捨てた判断は、このプロジェクトにおいて最も価値のある決断だったと確信しています。

AIは、膨大で難解な企業の開示資料から時系列を抽出し、専門用語を整理し、Markdownの骨組みを組み上げる作業において驚くべき速度を発揮してくれます。その作業を人間がゼロからやっていては、次々に発生する事件の記録が追いつきません。

しかし、**「その数字は本当に確定値なのか」「この表現は企業の発表の範囲を逸脱していないか」「これをいま公開して誰かに不当な害が及ばないか」**という最後の責任を引き受けることは、AIにはできません。自動化すべきなのは「情報を取り出して整理する作業の手間」であって、「世に出すかどうかの判断」を自動化してはならなかったのです。

全自動を諦めて人間が間に入ることで、私たちは胸を張って「ここには確かな一次情報しか載っていない」と言えるサイトを届けることができるようになりました。

このノートに出てくるプロダクト

ほかのノート

ノートの一覧に戻る